get(route('admin.bookings.pending')) ->assertRedirect(route('login')); }); test('client cannot access pending bookings page', function () { $client = User::factory()->individual()->create(); $this->actingAs($client) ->get(route('admin.bookings.pending')) ->assertForbidden(); }); test('admin can access pending bookings page', function () { $admin = User::factory()->admin()->create(); $this->actingAs($admin) ->get(route('admin.bookings.pending')) ->assertOk(); }); test('pending bookings list displays pending consultations', function () { $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.pending') ->assertSee($client->full_name); }); test('pending bookings list does not show non-pending consultations', function () { $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); Consultation::factory()->approved()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.pending') ->assertDontSee($client->full_name); }); test('admin can filter bookings by date range', function () { $admin = User::factory()->admin()->create(); $oldClient = User::factory()->individual()->create(['full_name' => 'Old Client']); $newClient = User::factory()->individual()->create(['full_name' => 'New Client']); Consultation::factory()->pending()->create([ 'user_id' => $oldClient->id, 'booking_date' => now()->subDays(10), ]); Consultation::factory()->pending()->create([ 'user_id' => $newClient->id, 'booking_date' => now()->addDays(5), ]); $this->actingAs($admin); Volt::test('admin.bookings.pending') ->set('dateFrom', now()->format('Y-m-d')) ->assertSee('New Client') ->assertDontSee('Old Client'); }); test('admin can access booking review page', function () { $admin = User::factory()->admin()->create(); $consultation = Consultation::factory()->pending()->create(); $this->actingAs($admin) ->get(route('admin.bookings.review', $consultation)) ->assertOk(); }); test('admin can approve booking as free consultation', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->set('consultationType', 'free') ->call('approve') ->assertHasNoErrors() ->assertRedirect(route('admin.bookings.pending')); expect($consultation->fresh()) ->status->toBe(ConsultationStatus::Approved) ->consultation_type->toBe(ConsultationType::Free) ->payment_status->toBe(PaymentStatus::NotApplicable); Notification::assertSentTo($client, BookingApproved::class); }); test('admin can approve booking as paid consultation with amount', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->set('consultationType', 'paid') ->set('paymentAmount', '150.00') ->set('paymentInstructions', 'Bank transfer to account XYZ') ->call('approve') ->assertHasNoErrors(); expect($consultation->fresh()) ->status->toBe(ConsultationStatus::Approved) ->consultation_type->toBe(ConsultationType::Paid) ->payment_amount->toBe('150.00') ->payment_status->toBe(PaymentStatus::Pending); }); test('paid consultation requires payment amount', function () { $admin = User::factory()->admin()->create(); $consultation = Consultation::factory()->pending()->create(); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->set('consultationType', 'paid') ->set('paymentAmount', null) ->call('approve') ->assertHasErrors(['paymentAmount']); }); test('admin can reject booking with reason', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->set('rejectionReason', 'Schedule conflict') ->call('reject') ->assertHasNoErrors() ->assertRedirect(route('admin.bookings.pending')); expect($consultation->fresh())->status->toBe(ConsultationStatus::Rejected); Notification::assertSentTo($client, BookingRejected::class); }); test('admin can reject booking without reason', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->call('reject') ->assertHasNoErrors(); expect($consultation->fresh())->status->toBe(ConsultationStatus::Rejected); }); test('quick approve from list works', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.pending') ->call('openApproveModal', $consultation->id) ->assertSet('showApproveModal', true) ->assertSet('approvingBookingId', $consultation->id) ->set('consultationType', 'free') ->call('quickApprove') ->assertHasNoErrors(); expect($consultation->fresh()) ->status->toBe(ConsultationStatus::Approved) ->consultation_type->toBe(ConsultationType::Free); Notification::assertSentTo($client, BookingApproved::class); }); test('quick reject from list works', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.pending') ->call('quickReject', $consultation->id) ->assertHasNoErrors(); expect($consultation->fresh())->status->toBe(ConsultationStatus::Rejected); Notification::assertSentTo($client, BookingRejected::class); }); test('audit log entry created on approval', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->set('consultationType', 'free') ->call('approve'); expect(AdminLog::query() ->where('admin_id', $admin->id) ->where('action', 'approve') ->where('target_type', 'consultation') ->where('target_id', $consultation->id) ->exists() )->toBeTrue(); }); test('audit log entry created on rejection', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->set('rejectionReason', 'Not available') ->call('reject'); expect(AdminLog::query() ->where('admin_id', $admin->id) ->where('action', 'reject') ->where('target_type', 'consultation') ->where('target_id', $consultation->id) ->exists() )->toBeTrue(); }); test('cannot approve already approved booking', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $consultation = Consultation::factory()->approved()->create(); $this->actingAs($admin); // Ensure status doesn't change Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->call('approve'); // Verify consultation status is still approved (not changed) expect($consultation->fresh()->status)->toBe(ConsultationStatus::Approved); // Verify no duplicate notification was sent Notification::assertNothingSent(); }); test('cannot reject already rejected booking', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $consultation = Consultation::factory()->create([ 'status' => ConsultationStatus::Rejected, ]); $this->actingAs($admin); // Ensure status doesn't change Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->call('reject'); // Verify consultation status is still rejected (not changed) expect($consultation->fresh()->status)->toBe(ConsultationStatus::Rejected); // Verify no notification was sent Notification::assertNothingSent(); }); test('booking details view shows client consultation history', function () { $admin = User::factory()->admin()->create(); $client = User::factory()->individual()->create(); // Create past consultations Consultation::factory()->completed()->create([ 'user_id' => $client->id, 'booking_date' => now()->subMonth(), ]); // Create current pending consultation $consultation = Consultation::factory()->pending()->create([ 'user_id' => $client->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->assertOk(); }); test('notification sent in client preferred language arabic', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $arabicClient = User::factory()->individual()->create([ 'preferred_language' => 'ar', ]); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $arabicClient->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->set('consultationType', 'free') ->call('approve'); Notification::assertSentTo($arabicClient, BookingApproved::class, function ($notification) { return $notification->consultation->user->preferred_language === 'ar'; }); }); test('notification sent in client preferred language english', function () { Notification::fake(); $admin = User::factory()->admin()->create(); $englishClient = User::factory()->individual()->create([ 'preferred_language' => 'en', ]); $consultation = Consultation::factory()->pending()->create([ 'user_id' => $englishClient->id, ]); $this->actingAs($admin); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->set('consultationType', 'free') ->call('approve'); Notification::assertSentTo($englishClient, BookingApproved::class, function ($notification) { return $notification->consultation->user->preferred_language === 'en'; }); });