Initial Ladill Frontdesk release with deploy pipeline.
Visitor management app with SSO, kiosk, badges, reports, and Gitea CI deploy to frontdesk.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user