approved()->create([ 'booking_date' => now()->addHours(24)->toDateString(), 'booking_time' => now()->addHours(24)->format('H:i:s'), 'reminder_24h_sent_at' => null, ]); $this->artisan('reminders:send-24h') ->assertSuccessful(); Notification::assertSentTo( $consultation->user, ConsultationReminder24h::class ); expect($consultation->fresh()->reminder_24h_sent_at)->not->toBeNull(); }); it('does not send 24h reminder for cancelled consultation', function () { Notification::fake(); $consultation = Consultation::factory()->cancelled()->create([ 'booking_date' => now()->addHours(24)->toDateString(), 'booking_time' => now()->addHours(24)->format('H:i:s'), ]); $this->artisan('reminders:send-24h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder24h::class); }); it('does not send 24h reminder for no-show consultation', function () { Notification::fake(); $consultation = Consultation::factory()->noShow()->create([ 'booking_date' => now()->addHours(24)->toDateString(), 'booking_time' => now()->addHours(24)->format('H:i:s'), ]); $this->artisan('reminders:send-24h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder24h::class); }); it('does not send duplicate 24h reminders', function () { Notification::fake(); $consultation = Consultation::factory()->approved()->create([ 'booking_date' => now()->addHours(24)->toDateString(), 'booking_time' => now()->addHours(24)->format('H:i:s'), 'reminder_24h_sent_at' => now()->subHour(), ]); $this->artisan('reminders:send-24h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder24h::class); }); it('sends 2h reminder for upcoming consultation', function () { Notification::fake(); $consultation = Consultation::factory()->approved()->create([ 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), 'reminder_2h_sent_at' => null, ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertSentTo( $consultation->user, ConsultationReminder2h::class ); expect($consultation->fresh()->reminder_2h_sent_at)->not->toBeNull(); }); it('does not send 2h reminder for cancelled consultation', function () { Notification::fake(); $consultation = Consultation::factory()->cancelled()->create([ 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder2h::class); }); it('does not send duplicate 2h reminders', function () { Notification::fake(); $consultation = Consultation::factory()->approved()->create([ 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), 'reminder_2h_sent_at' => now()->subMinutes(30), ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder2h::class); }); it('includes payment reminder for unpaid consultations in 24h reminder', function () { Notification::fake(); $consultation = Consultation::factory()->approved()->paid()->create([ 'booking_date' => now()->addHours(24)->toDateString(), 'booking_time' => now()->addHours(24)->format('H:i:s'), 'payment_status' => 'pending', 'payment_amount' => 200.00, ]); $this->artisan('reminders:send-24h') ->assertSuccessful(); Notification::assertSentTo( $consultation->user, ConsultationReminder24h::class, function ($notification) { return $notification->consultation->consultation_type->value === 'paid' && $notification->consultation->payment_status->value === 'pending'; } ); }); it('respects user language preference for 24h reminders', function () { Notification::fake(); $user = User::factory()->create(['preferred_language' => 'en']); $consultation = Consultation::factory()->approved()->create([ 'user_id' => $user->id, 'booking_date' => now()->addHours(24)->toDateString(), 'booking_time' => now()->addHours(24)->format('H:i:s'), ]); $this->artisan('reminders:send-24h') ->assertSuccessful(); Notification::assertSentTo($user, ConsultationReminder24h::class); }); it('does not send 24h reminder for pending consultation', function () { Notification::fake(); $consultation = Consultation::factory()->pending()->create([ 'booking_date' => now()->addHours(24)->toDateString(), 'booking_time' => now()->addHours(24)->format('H:i:s'), ]); $this->artisan('reminders:send-24h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder24h::class); }); it('does not send 24h reminder for completed consultation', function () { Notification::fake(); $consultation = Consultation::factory()->completed()->create([ 'booking_date' => now()->addHours(24)->toDateString(), 'booking_time' => now()->addHours(24)->format('H:i:s'), ]); $this->artisan('reminders:send-24h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder24h::class); }); it('does not send 2h reminder for rejected consultation', function () { Notification::fake(); $consultation = Consultation::factory()->rejected()->create([ 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder2h::class); }); // 2-Hour Reminder Additional Tests (Story 8.7) it('2h command uses 7-minute window for matching', function () { Notification::fake(); // Create consultation at exactly 2h + 8 minutes (outside window) $outsideWindow = Consultation::factory()->approved()->create([ 'booking_date' => now()->addHours(2)->addMinutes(8)->toDateString(), 'booking_time' => now()->addHours(2)->addMinutes(8)->format('H:i:s'), 'reminder_2h_sent_at' => null, ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertNotSentTo($outsideWindow->user, ConsultationReminder2h::class); }); it('2h command sends reminder within 7-minute window', function () { Notification::fake(); // Create consultation at exactly 2h + 6 minutes (inside window) $insideWindow = Consultation::factory()->approved()->create([ 'booking_date' => now()->addHours(2)->addMinutes(6)->toDateString(), 'booking_time' => now()->addHours(2)->addMinutes(6)->format('H:i:s'), 'reminder_2h_sent_at' => null, ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertSentTo($insideWindow->user, ConsultationReminder2h::class); }); it('2h command only checks consultations scheduled for today', function () { Notification::fake(); // Create consultation 2 hours from now but for tomorrow's date $tomorrowConsultation = Consultation::factory()->approved()->create([ 'booking_date' => now()->addDay()->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), 'reminder_2h_sent_at' => null, ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertNotSentTo($tomorrowConsultation->user, ConsultationReminder2h::class); }); it('does not send 2h reminder for pending consultation', function () { Notification::fake(); $consultation = Consultation::factory()->pending()->create([ 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder2h::class); }); it('does not send 2h reminder for completed consultation', function () { Notification::fake(); $consultation = Consultation::factory()->completed()->create([ 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder2h::class); }); it('does not send 2h reminder for no-show consultation', function () { Notification::fake(); $consultation = Consultation::factory()->noShow()->create([ 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertNotSentTo($consultation->user, ConsultationReminder2h::class); }); it('respects user language preference for 2h reminders', function () { Notification::fake(); $user = User::factory()->create(['preferred_language' => 'en']); $consultation = Consultation::factory()->approved()->create([ 'user_id' => $user->id, 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertSentTo($user, ConsultationReminder2h::class); }); it('shows payment reminder for unpaid paid consultation in 2h reminder', function () { Notification::fake(); $consultation = Consultation::factory()->approved()->paid()->create([ 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), 'payment_status' => 'pending', 'payment_amount' => 200.00, ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertSentTo( $consultation->user, ConsultationReminder2h::class, function ($notification) { return $notification->consultation->consultation_type->value === 'paid' && $notification->consultation->payment_status->value === 'pending'; } ); }); it('hides payment reminder when payment received in 2h reminder', function () { Notification::fake(); $consultation = Consultation::factory()->approved()->paid()->create([ 'booking_date' => now()->addHours(2)->toDateString(), 'booking_time' => now()->addHours(2)->format('H:i:s'), 'payment_status' => 'received', 'payment_amount' => 200.00, ]); $this->artisan('reminders:send-2h') ->assertSuccessful(); Notification::assertSentTo( $consultation->user, ConsultationReminder2h::class, function ($notification) { return $notification->consultation->payment_status->value === 'received'; } ); }); // 2-Hour Reminder Email Content Tests (Story 8.7) it('2h reminder notification has correct subject in Arabic', function () { $user = User::factory()->create(['preferred_language' => 'ar']); $consultation = Consultation::factory()->approved()->create([ 'user_id' => $user->id, ]); $notification = new ConsultationReminder2h($consultation); $mail = $notification->toMail($user); expect($mail->subject)->toBe('استشارتك بعد ساعتين'); }); it('2h reminder notification has correct subject in English', function () { $user = User::factory()->create(['preferred_language' => 'en']); $consultation = Consultation::factory()->approved()->create([ 'user_id' => $user->id, ]); $notification = new ConsultationReminder2h($consultation); $mail = $notification->toMail($user); expect($mail->subject)->toBe('Your consultation is in 2 hours'); }); it('2h reminder email does not include calendar download link', function () { $user = User::factory()->create(['preferred_language' => 'en']); $consultation = Consultation::factory()->approved()->create([ 'user_id' => $user->id, ]); $notification = new ConsultationReminder2h($consultation); $mail = $notification->toMail($user); // The view should be emails.reminder-2h which doesn't have calendar link expect($mail->view)->toBe('emails.reminder-2h'); }); it('2h reminder notification is queued', function () { expect(ConsultationReminder2h::class) ->toImplement(\Illuminate\Contracts\Queue\ShouldQueue::class); });