Visitor management app with SSO, kiosk, badges, reports, and Gitea CI deploy to frontdesk.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
220 lines
6.9 KiB
PHP
220 lines
6.9 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Http\Middleware\EnsurePlatformSession;
|
|
use App\Models\AuditLog;
|
|
use App\Models\Branch;
|
|
use App\Models\Member;
|
|
use App\Models\Organization;
|
|
use App\Models\User;
|
|
use App\Models\Visit;
|
|
use App\Models\Visitor;
|
|
use App\Models\WatchlistEntry;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Carbon;
|
|
use Tests\TestCase;
|
|
|
|
class FrontdeskPhase5Test extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected User $user;
|
|
|
|
protected Organization $organization;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutMiddleware(EnsurePlatformSession::class);
|
|
|
|
$this->user = User::create([
|
|
'public_id' => 'phase5-user-001',
|
|
'name' => 'Phase5 User',
|
|
'email' => 'phase5@example.com',
|
|
]);
|
|
|
|
$this->organization = Organization::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'name' => 'Phase5 Org',
|
|
'slug' => 'phase5-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',
|
|
]);
|
|
|
|
Branch::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'name' => 'HQ',
|
|
'is_active' => true,
|
|
]);
|
|
}
|
|
|
|
public function test_blacklisted_check_in_records_audit_log(): void
|
|
{
|
|
$visitor = Visitor::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'full_name' => 'Blocked Person',
|
|
'email' => 'blocked@example.com',
|
|
'watchlist_status' => Visitor::WATCHLIST_BLACKLISTED,
|
|
]);
|
|
|
|
$this->actingAs($this->user)
|
|
->post(route('frontdesk.visits.store'), [
|
|
'visitor_id' => $visitor->id,
|
|
'full_name' => 'Blocked Person',
|
|
'email' => 'blocked@example.com',
|
|
'visitor_type' => 'visitor',
|
|
'policies_accepted' => '1',
|
|
])
|
|
->assertForbidden();
|
|
|
|
$this->assertDatabaseHas('frontdesk_audit_logs', [
|
|
'action' => 'watchlist.blocked_attempt',
|
|
]);
|
|
}
|
|
|
|
public function test_flagged_visitor_check_in_queues_for_approval(): void
|
|
{
|
|
$visitor = Visitor::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'full_name' => 'Flagged Person',
|
|
'watchlist_status' => Visitor::WATCHLIST_REQUIRES_APPROVAL,
|
|
]);
|
|
|
|
$this->actingAs($this->user)
|
|
->post(route('frontdesk.visitors.check-in.store', $visitor), [
|
|
'visitor_type' => 'visitor',
|
|
'policies_accepted' => '1',
|
|
])
|
|
->assertRedirect();
|
|
|
|
$visit = Visit::first();
|
|
$this->assertSame(Visit::STATUS_WAITING, $visit->status);
|
|
$this->assertTrue($visit->awaitingApproval());
|
|
|
|
$this->assertDatabaseHas('frontdesk_audit_logs', [
|
|
'action' => 'watchlist.flagged_checkin',
|
|
]);
|
|
}
|
|
|
|
public function test_watchlist_admin_can_create_entry(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->post(route('frontdesk.watchlist.store'), [
|
|
'full_name' => 'Known Person',
|
|
'status' => Visitor::WATCHLIST_BLACKLISTED,
|
|
'reason' => 'Previous incident',
|
|
])
|
|
->assertRedirect(route('frontdesk.watchlist.index'));
|
|
|
|
$this->assertDatabaseHas('frontdesk_watchlist_entries', [
|
|
'full_name' => 'Known Person',
|
|
'status' => Visitor::WATCHLIST_BLACKLISTED,
|
|
]);
|
|
}
|
|
|
|
public function test_audit_log_page_loads_and_exports_csv(): void
|
|
{
|
|
AuditLog::record(
|
|
$this->user->public_id,
|
|
'visit.checked_in',
|
|
$this->organization->id,
|
|
$this->user->public_id,
|
|
Visit::class,
|
|
1,
|
|
['visitor' => 'Test'],
|
|
);
|
|
|
|
$this->actingAs($this->user)
|
|
->get(route('frontdesk.audit.index'))
|
|
->assertOk()
|
|
->assertSee('Audit log');
|
|
|
|
$this->actingAs($this->user)
|
|
->get(route('frontdesk.audit.export'))
|
|
->assertOk()
|
|
->assertHeader('content-type', 'text/csv; charset=UTF-8');
|
|
}
|
|
|
|
public function test_security_can_verify_badge_code(): void
|
|
{
|
|
$visitor = Visitor::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'full_name' => 'Verify Me',
|
|
]);
|
|
|
|
$visit = Visit::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'visitor_id' => $visitor->id,
|
|
'visitor_type' => 'visitor',
|
|
'status' => Visit::STATUS_CHECKED_IN,
|
|
'badge_code' => 'ABC12345',
|
|
'checked_in_at' => now(),
|
|
'badge_expires_at' => now()->addHours(4),
|
|
]);
|
|
|
|
$this->actingAs($this->user)
|
|
->post(route('frontdesk.security.verify.lookup'), ['lookup' => 'ABC12345'])
|
|
->assertOk()
|
|
->assertSee('Verify Me')
|
|
->assertSee('Valid');
|
|
}
|
|
|
|
public function test_soft_deleted_visitor_can_be_restored(): void
|
|
{
|
|
$visitor = Visitor::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'full_name' => 'Archived Person',
|
|
]);
|
|
|
|
$visitor->delete();
|
|
|
|
$this->actingAs($this->user)
|
|
->post(route('frontdesk.compliance.visitors.restore', $visitor->id))
|
|
->assertRedirect();
|
|
|
|
$this->assertNull($visitor->fresh()->deleted_at);
|
|
$this->assertDatabaseHas('frontdesk_audit_logs', ['action' => 'visitor.restored']);
|
|
}
|
|
|
|
public function test_expired_badge_command_logs_alert(): void
|
|
{
|
|
Carbon::setTestNow('2026-06-27 18:00:00');
|
|
|
|
$visitor = Visitor::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'full_name' => 'Expired Badge Guest',
|
|
]);
|
|
|
|
Visit::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'visitor_id' => $visitor->id,
|
|
'visitor_type' => 'visitor',
|
|
'status' => Visit::STATUS_CHECKED_IN,
|
|
'checked_in_at' => now()->subHours(10),
|
|
'badge_expires_at' => now()->subHour(),
|
|
'badge_code' => 'EXP12345',
|
|
]);
|
|
|
|
$this->artisan('frontdesk:mark-expired-badges')->assertSuccessful();
|
|
|
|
$this->assertDatabaseHas('frontdesk_audit_logs', ['action' => 'badge.expired']);
|
|
|
|
Carbon::setTestNow();
|
|
}
|
|
}
|