Deploy Ladill Frontdesk / deploy (push) Successful in 30s
Replace the static kiosk/printer token message with dynamic hints per device type so reception computers are not misread as getting unattended access. Co-authored-by: Cursor <cursoragent@cursor.com>
203 lines
6.5 KiB
PHP
203 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Http\Middleware\EnsurePlatformSession;
|
|
use App\Models\Branch;
|
|
use App\Models\Device;
|
|
use App\Models\Host;
|
|
use App\Models\Member;
|
|
use App\Models\Organization;
|
|
use App\Models\User;
|
|
use App\Models\Visit;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Tests\TestCase;
|
|
|
|
class FrontdeskPhase7Test extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected User $user;
|
|
|
|
protected Organization $organization;
|
|
|
|
protected string $deviceToken;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutMiddleware(EnsurePlatformSession::class);
|
|
|
|
$this->user = User::create([
|
|
'public_id' => 'phase7-user-001',
|
|
'name' => 'Phase7 User',
|
|
'email' => 'phase7@example.com',
|
|
]);
|
|
|
|
$this->organization = Organization::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'name' => 'Phase7 Org',
|
|
'slug' => 'phase7-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,
|
|
]);
|
|
|
|
Host::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'name' => 'Lobby Host',
|
|
'is_available' => true,
|
|
]);
|
|
|
|
$this->deviceToken = 'kiosk-test-token-'.str_repeat('x', 32);
|
|
}
|
|
|
|
public function test_admin_can_register_kiosk_device(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->post(route('frontdesk.devices.store'), [
|
|
'name' => 'Lobby Kiosk',
|
|
'type' => 'kiosk',
|
|
])
|
|
->assertRedirect(route('frontdesk.devices.index'));
|
|
|
|
$device = Device::first();
|
|
$this->assertSame('kiosk', $device->type);
|
|
$this->assertNotEmpty($device->device_token);
|
|
}
|
|
|
|
public function test_device_create_page_shows_type_specific_registration_hints(): void
|
|
{
|
|
$response = $this->actingAs($this->user)->get(route('frontdesk.devices.create'));
|
|
|
|
$response->assertOk();
|
|
$response->assertSee('Tracked for your team only', false);
|
|
$response->assertSee('Open the kiosk URL on your tablet', false);
|
|
$response->assertSee('x-model="type"', false);
|
|
$response->assertDontSee('Kiosks and printers receive a device token for unattended access.', false);
|
|
}
|
|
|
|
public function test_reception_computer_registration_does_not_issue_token(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->post(route('frontdesk.devices.store'), [
|
|
'name' => 'Front desk PC',
|
|
'type' => 'reception_computer',
|
|
])
|
|
->assertRedirect(route('frontdesk.devices.index'));
|
|
|
|
$device = Device::first();
|
|
$this->assertSame('reception_computer', $device->type);
|
|
$this->assertNull($device->device_token);
|
|
}
|
|
|
|
public function test_kiosk_device_page_works_without_auth(): void
|
|
{
|
|
$device = $this->createKioskDevice();
|
|
|
|
$this->get(route('frontdesk.kiosk.device', $device->device_token))
|
|
->assertOk()
|
|
->assertSee('Visitor Check-in')
|
|
->assertSee('Welcome to')
|
|
->assertSee('Tap to begin');
|
|
|
|
$this->assertTrue($device->fresh()->isOnline());
|
|
}
|
|
|
|
public function test_kiosk_device_page_shows_custom_organization_logo(): void
|
|
{
|
|
Storage::fake('public');
|
|
$path = UploadedFile::fake()->image('brand.png', 320, 80)->store('frontdesk/organizations/'.$this->organization->id, 'public');
|
|
$this->organization->update(['logo_path' => $path]);
|
|
|
|
$device = $this->createKioskDevice();
|
|
|
|
$this->get(route('frontdesk.kiosk.device', $device->device_token))
|
|
->assertOk()
|
|
->assertSee(Storage::disk('public')->url($path), false);
|
|
}
|
|
|
|
public function test_kiosk_device_can_check_in_visitor(): void
|
|
{
|
|
$device = $this->createKioskDevice();
|
|
$host = Host::first();
|
|
|
|
$this->postJson(route('frontdesk.kiosk.device.check-in', $device->device_token), [
|
|
'full_name' => 'Kiosk Guest',
|
|
'host_id' => $host->id,
|
|
'visitor_type' => 'visitor',
|
|
'policies_accepted' => 1,
|
|
])->assertOk()
|
|
->assertJsonPath('visit.visitor_name', 'Kiosk Guest');
|
|
|
|
$visit = Visit::first();
|
|
$this->assertSame(Visit::STATUS_CHECKED_IN, $visit->status);
|
|
$this->assertSame($device->branch_id, $visit->branch_id);
|
|
}
|
|
|
|
public function test_device_heartbeat_api_marks_online(): void
|
|
{
|
|
$device = $this->createKioskDevice(['status' => 'offline', 'last_online_at' => null]);
|
|
|
|
$this->postJson('/api/devices/heartbeat', [], [
|
|
'X-Device-Token' => $device->device_token,
|
|
])->assertOk()
|
|
->assertJsonPath('status', 'online');
|
|
|
|
$this->assertTrue($device->fresh()->isOnline());
|
|
}
|
|
|
|
public function test_mark_devices_offline_command(): void
|
|
{
|
|
Carbon::setTestNow(now());
|
|
|
|
$device = $this->createKioskDevice([
|
|
'status' => 'online',
|
|
'last_online_at' => now()->subMinutes(30),
|
|
]);
|
|
|
|
$this->artisan('frontdesk:mark-devices-offline')->assertSuccessful();
|
|
|
|
$this->assertSame('offline', $device->fresh()->status);
|
|
|
|
Carbon::setTestNow();
|
|
}
|
|
|
|
public function test_invalid_device_token_returns_not_found(): void
|
|
{
|
|
$this->get(route('frontdesk.kiosk.device', 'invalid-token'))
|
|
->assertNotFound();
|
|
}
|
|
|
|
/** @param array<string, mixed> $overrides */
|
|
protected function createKioskDevice(array $overrides = []): Device
|
|
{
|
|
return Device::create(array_merge([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'branch_id' => Branch::first()->id,
|
|
'name' => 'Test Kiosk',
|
|
'type' => 'kiosk',
|
|
'status' => 'offline',
|
|
'device_token' => $this->deviceToken,
|
|
'config' => ['mode' => 'self_service'],
|
|
], $overrides));
|
|
}
|
|
}
|