create([ 'day_of_week' => 1, 'start_time' => '09:00', 'end_time' => '17:00', 'is_active' => true, ]); }); // Navigation Tests test('client sees book now link in navigation', function () { $client = User::factory()->individual()->create(); $this->actingAs($client) ->get(route('client.dashboard')) ->assertOk() ->assertSee(__('navigation.book_now')); }); test('book now link navigates to booking page', function () { $client = User::factory()->individual()->create(); $this->actingAs($client) ->get(route('client.consultations.book')) ->assertOk(); }); test('admin does not see book now link', function () { $admin = User::factory()->admin()->create(); $this->actingAs($admin) ->get(route('admin.dashboard')) ->assertOk() ->assertDontSee(__('navigation.book_now')); }); test('guest cannot access booking form', function () { $this->get(route('client.consultations.book')) ->assertRedirect(route('login')); }); test('authenticated client can access booking form', function () { $client = User::factory()->individual()->create(); $this->actingAs($client) ->get(route('client.consultations.book')) ->assertOk(); }); test('authenticated client can submit booking request', function () { Mail::fake(); $client = User::factory()->individual()->create(); User::factory()->admin()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute with my employer.') ->call('showConfirm') ->assertSet('showConfirmation', true) ->call('submit') ->assertRedirect(route('client.consultations.index')); expect(Consultation::where('user_id', $client->id)->exists())->toBeTrue(); Mail::assertQueued(BookingSubmittedMail::class); Mail::assertQueued(NewBookingRequestMail::class); }); test('booking is created with pending status', function () { Mail::fake(); $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->call('submit'); $consultation = Consultation::where('user_id', $client->id)->first(); expect($consultation->status)->toBe(ConsultationStatus::Pending); }); test('problem summary is required', function () { $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', '') ->call('showConfirm') ->assertHasErrors(['problemSummary' => 'required']); }); test('problem summary must be at least 20 characters', function () { $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'Too short') ->call('showConfirm') ->assertHasErrors(['problemSummary' => 'min']); }); test('problem summary cannot exceed 2000 characters', function () { $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', str_repeat('a', 2001)) ->call('showConfirm') ->assertHasErrors(['problemSummary' => 'max']); }); test('client cannot book more than once per day', function () { $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); // Create existing booking Consultation::factory()->pending()->create([ 'user_id' => $client->id, 'booking_date' => $monday, 'booking_time' => '09:00', ]); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->assertHasErrors(['selectedDate']); }); test('client can book on different day if already booked on another', function () { Mail::fake(); $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $nextMonday = now()->next('Monday')->addWeek()->format('Y-m-d'); // Create existing booking for this Monday Consultation::factory()->pending()->create([ 'user_id' => $client->id, 'booking_date' => $monday, 'booking_time' => '09:00', ]); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $nextMonday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->assertSet('showConfirmation', true); }); test('client cannot book unavailable slot', function () { $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); // Create booking that takes the slot Consultation::factory()->approved()->create([ 'booking_date' => $monday, 'booking_time' => '10:00', ]); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->assertHasErrors(['selectedTime']); }); test('confirmation step displays before final submission', function () { $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->assertSet('showConfirmation', false) ->call('showConfirm') ->assertSet('showConfirmation', true); }); test('user can go back from confirmation to edit', function () { $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->assertSet('showConfirmation', true) ->set('showConfirmation', false) ->assertSet('showConfirmation', false); }); test('user can clear slot selection', function () { $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->assertSet('selectedDate', $monday) ->assertSet('selectedTime', '10:00') ->call('clearSelection') ->assertSet('selectedDate', null) ->assertSet('selectedTime', null); }); test('success message shown after submission', function () { Mail::fake(); $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->call('submit') ->assertSessionHas('success'); }); test('audit log entry is created on booking submission', function () { Mail::fake(); $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->call('submit'); $consultation = Consultation::where('user_id', $client->id)->first(); expect(AdminLog::query() ->where('action', 'create') ->where('target_type', 'consultation') ->where('target_id', $consultation->id) ->whereNull('admin_id') ->exists() )->toBeTrue(); }); test('emails are sent to client and admin after submission', function () { Mail::fake(); $client = User::factory()->individual()->create(); $admin = User::factory()->admin()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->call('submit'); Mail::assertQueued(BookingSubmittedMail::class, function ($mail) use ($client) { return $mail->hasTo($client->email); }); Mail::assertQueued(NewBookingRequestMail::class, function ($mail) use ($admin) { return $mail->hasTo($admin->email); }); }); test('booking fails if slot is taken during submission (race condition)', function () { Mail::fake(); $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); $component = Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->assertSet('showConfirmation', true); // Another user takes the slot Consultation::factory()->approved()->create([ 'booking_date' => $monday, 'booking_time' => '10:00', ]); $component->call('submit') ->assertHasErrors(['selectedTime']) ->assertSet('showConfirmation', false); expect(Consultation::where('user_id', $client->id)->exists())->toBeFalse(); }); test('booking fails if user already booked during submission (race condition)', function () { Mail::fake(); $client = User::factory()->individual()->create(); $monday = now()->next('Monday')->format('Y-m-d'); $this->actingAs($client); $component = Volt::test('client.consultations.book') ->call('selectSlot', $monday, '10:00') ->set('problemSummary', 'I need legal advice regarding a contract dispute.') ->call('showConfirm') ->assertSet('showConfirmation', true); // User books another slot on same day (from another browser tab) Consultation::factory()->pending()->create([ 'user_id' => $client->id, 'booking_date' => $monday, 'booking_time' => '11:00', ]); $component->call('submit') ->assertHasErrors(['selectedTime']) ->assertSet('showConfirmation', false); // Should only have the one booking from the "other tab" expect(Consultation::where('user_id', $client->id)->count())->toBe(1); });