withoutMiddleware(EnsurePlatformSession::class); $this->user = User::create([ 'public_id' => 'phase3-user-001', 'name' => 'Phase3 User', 'email' => 'phase3@example.com', ]); $this->organization = Organization::create([ 'owner_ref' => $this->user->public_id, 'name' => 'Phase3 Org', 'slug' => 'phase3-org', 'settings' => ['onboarded' => true, 'badge_expiry_hours' => 8], ]); Member::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->user->public_id, 'role' => 'org_admin', ]); $this->branch = Branch::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'name' => 'HQ', 'is_active' => true, ]); } public function test_can_schedule_future_visit(): void { $scheduledAt = now()->addDay()->format('Y-m-d\TH:i'); $this->actingAs($this->user) ->post(route('frontdesk.visits.schedule.store'), [ 'full_name' => 'Future Guest', 'email' => 'future@example.com', 'visitor_type' => 'visitor', 'scheduled_at' => $scheduledAt, 'purpose' => 'Meeting', ]) ->assertRedirect(); $this->assertDatabaseHas('frontdesk_visits', [ 'status' => Visit::STATUS_SCHEDULED, 'purpose' => 'Meeting', ]); } public function test_can_schedule_today_visit_as_expected(): void { $scheduledAt = now()->addHour()->format('Y-m-d\TH:i'); $this->actingAs($this->user) ->post(route('frontdesk.visits.schedule.store'), [ 'full_name' => 'Today Guest', 'email' => 'today@example.com', 'visitor_type' => 'visitor', 'scheduled_at' => $scheduledAt, ]) ->assertRedirect(); $this->assertDatabaseHas('frontdesk_visits', [ 'status' => Visit::STATUS_EXPECTED, ]); } public function test_can_check_in_scheduled_visit(): void { $visitor = Visitor::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'full_name' => 'Scheduled Guest', 'email' => 'scheduled@example.com', ]); $visit = Visit::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'visitor_id' => $visitor->id, 'visitor_type' => 'visitor', 'status' => Visit::STATUS_EXPECTED, 'scheduled_at' => now()->addHour(), ]); $this->actingAs($this->user) ->post(route('frontdesk.visits.activate', $visit), [ 'policies_accepted' => '1', ]) ->assertRedirect(route('frontdesk.visits.show', $visit)); $visit->refresh(); $this->assertSame(Visit::STATUS_CHECKED_IN, $visit->status); $this->assertNotNull($visit->checked_in_at); } public function test_can_mark_visit_waiting_and_cancel(): void { $visitor = Visitor::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'full_name' => 'Waiting Guest', ]); $visit = Visit::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'visitor_id' => $visitor->id, 'visitor_type' => 'visitor', 'status' => Visit::STATUS_EXPECTED, 'scheduled_at' => now(), ]); $this->actingAs($this->user) ->post(route('frontdesk.visits.waiting', $visit)) ->assertRedirect(); $this->assertSame(Visit::STATUS_WAITING, $visit->fresh()->status); $this->actingAs($this->user) ->post(route('frontdesk.visits.cancel', $visit), ['reason' => 'No show']) ->assertRedirect(route('frontdesk.visits.index', ['status' => 'cancelled'])); $this->assertSame(Visit::STATUS_CANCELLED, $visit->fresh()->status); } public function test_returning_visitor_quick_check_in(): void { $visitor = Visitor::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'full_name' => 'Regular Guest', 'visit_count' => 3, ]); $this->actingAs($this->user) ->post(route('frontdesk.visitors.check-in.store', $visitor), [ 'visitor_type' => 'visitor', 'policies_accepted' => '1', ]) ->assertRedirect(); $this->assertDatabaseHas('frontdesk_visits', [ 'visitor_id' => $visitor->id, 'status' => Visit::STATUS_CHECKED_IN, ]); } public function test_can_update_visitor_profile(): void { $visitor = Visitor::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'full_name' => 'Profile Guest', 'notes' => 'Old note', ]); $this->actingAs($this->user) ->patch(route('frontdesk.visitors.update', $visitor), [ 'full_name' => 'Updated Guest', 'company' => 'Acme Corp', 'notes' => 'VIP guest', ]) ->assertRedirect(); $visitor->refresh(); $this->assertSame('Updated Guest', $visitor->full_name); $this->assertSame('Acme Corp', $visitor->company); $this->assertSame('VIP guest', $visitor->notes); } public function test_mark_overdue_command_updates_past_visits(): void { Carbon::setTestNow('2026-06-27 12:00:00'); $visitor = Visitor::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'full_name' => 'Late Guest', ]); Visit::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'visitor_id' => $visitor->id, 'visitor_type' => 'visitor', 'status' => Visit::STATUS_EXPECTED, 'scheduled_at' => now()->subHour(), ]); $this->artisan('frontdesk:mark-overdue-visits') ->assertSuccessful(); $this->assertDatabaseHas('frontdesk_visits', [ 'visitor_id' => $visitor->id, 'status' => Visit::STATUS_OVERDUE, ]); Carbon::setTestNow(); } public function test_calendar_page_loads(): void { $this->actingAs($this->user) ->get(route('frontdesk.visits.calendar')) ->assertOk() ->assertSee('Visit calendar'); } public function test_api_can_schedule_visit(): void { config(['frontdesk.service_api_keys.crm' => 'test-api-key']); $scheduledAt = now()->addDays(2)->toIso8601String(); $this->withHeader('Authorization', 'Bearer test-api-key') ->postJson('/api/visits?owner='.$this->user->public_id, [ 'schedule' => true, 'organization_id' => $this->organization->id, 'full_name' => 'API Guest', 'email' => 'api@example.com', 'visitor_type' => 'visitor', 'scheduled_at' => $scheduledAt, ]) ->assertCreated() ->assertJsonPath('status', Visit::STATUS_SCHEDULED); } }