withoutMiddleware(EnsurePlatformSession::class); $this->user = User::create([ 'public_id' => 'phase23-user', 'name' => 'Phase 23 User', 'email' => 'phase23@example.com', ]); $this->organization = Organization::create([ 'owner_ref' => $this->user->public_id, 'name' => 'Phase 23 Org', 'slug' => 'phase23-org', 'settings' => ['onboarded' => true, 'employee_kiosk_enabled' => true], ]); Member::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->user->public_id, 'role' => 'org_admin', ]); $this->homeBranch = Branch::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'name' => 'HQ', 'is_active' => true, ]); $this->otherBranch = Branch::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'name' => 'Annex', 'is_active' => true, ]); $this->deviceToken = 'kiosk-phase23-'.str_repeat('z', 32); Device::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->otherBranch->id, 'name' => 'Annex Kiosk', 'type' => 'kiosk', 'status' => 'offline', 'device_token' => $this->deviceToken, ]); } protected function createEmployee(array $overrides = []): Employee { $service = app(EmployeePresenceService::class); $employee = Employee::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->homeBranch->id, 'employee_code' => 'P23001', 'full_name' => 'Alex Staff', 'pin_hash' => $service->hashPin('5678'), 'active' => true, ...$overrides, ]); $service->presenceFor($employee); return $employee->fresh(); } public function test_kiosk_identifies_employee_by_qr_token(): void { $employee = $this->createEmployee(); $this->postJson(route('frontdesk.kiosk.device.staff.action', $this->deviceToken), [ 'qr_token' => $employee->qr_token, 'pin' => '5678', 'action' => 'identify', ])->assertOk() ->assertJsonPath('employee.full_name', 'Alex Staff'); } public function test_branch_mismatch_requires_confirmation(): void { $this->createEmployee(); $this->postJson(route('frontdesk.kiosk.device.staff.action', $this->deviceToken), [ 'employee_code' => 'P23001', 'pin' => '5678', 'action' => 'sign_in', ])->assertUnprocessable() ->assertJsonValidationErrors(['branch_mismatch']); $this->postJson(route('frontdesk.kiosk.device.staff.action', $this->deviceToken), [ 'employee_code' => 'P23001', 'pin' => '5678', 'action' => 'sign_in', 'confirm_branch_mismatch' => true, ])->assertOk() ->assertJsonPath('employee.status', 'on_site'); } public function test_employee_portal_step_out_without_kiosk(): void { $employee = $this->createEmployee(['user_ref' => $this->user->public_id]); app(EmployeePresenceService::class)->signIn($employee)['presence']; $this->actingAs($this->user) ->post(route('frontdesk.employee.portal.action'), [ 'action' => 'step_out', 'step_out_reason' => 'lunch', 'destination' => 'Cafeteria', ]) ->assertRedirect(route('frontdesk.employee.portal')); $this->assertDatabaseHas('frontdesk_employee_presence', [ 'employee_id' => $employee->id, 'status' => 'stepped_out', 'destination' => 'Cafeteria', ]); } public function test_evacuation_report_includes_staff(): void { $employee = $this->createEmployee(); app(EmployeePresenceService::class)->signIn($employee, null, true)['presence']; $this->actingAs($this->user) ->get(route('frontdesk.security.evacuation')) ->assertOk() ->assertSee('Alex Staff', false) ->assertSee('Staff', false); } public function test_employee_webhook_dispatched_on_sign_in(): void { Http::fake(); WebhookEndpoint::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'url' => 'https://example.com/hooks/frontdesk', 'secret' => 'secret', 'events' => ['employee.signed_in'], 'is_active' => true, ]); $employee = $this->createEmployee(['branch_id' => $this->otherBranch->id]); $this->postJson(route('frontdesk.kiosk.device.staff.action', $this->deviceToken), [ 'employee_code' => 'P23001', 'pin' => '5678', 'action' => 'sign_in', ])->assertOk(); Http::assertSent(fn ($request) => $request->url() === 'https://example.com/hooks/frontdesk' && $request['event'] === 'employee.signed_in' && $request['employee']['employee_code'] === 'P23001'); } public function test_hr_attendance_export(): void { $employee = $this->createEmployee(); app(EmployeePresenceService::class)->signIn($employee, null, true); $response = $this->actingAs($this->user) ->get(route('frontdesk.employees.export.attendance')); $response->assertOk(); $this->assertStringContainsString('Alex Staff', $response->streamedContent()); $this->assertStringContainsString('Sign in', $response->streamedContent()); } }