withoutMiddleware(EnsurePlatformSession::class); $this->withoutVite(); } private function user(): User { return User::create([ 'public_id' => 'u-'.uniqid(), 'name' => 'Owner', 'email' => uniqid().'@example.com', ]); } private function location(User $user): PosLocation { return PosLocation::create([ 'owner_ref' => $user->public_id, 'name' => 'Main register', 'currency' => 'GHS', ]); } public function test_can_register_register_device_without_token(): void { $user = $this->user(); $location = $this->location($user); $this->actingAs($user) ->post(route('pos.devices.store'), [ 'name' => 'Front till', 'type' => 'register', 'location_id' => $location->id, ]) ->assertRedirect(route('pos.devices.index')); $device = PosDevice::query()->where('owner_ref', $user->public_id)->first(); $this->assertNotNull($device); $this->assertSame('register', $device->type); $this->assertNull($device->device_token); } public function test_customer_display_device_reuses_branch_display_token(): void { $user = $this->user(); $location = $this->location($user); $this->actingAs($user) ->post(route('pos.devices.store'), [ 'name' => 'Counter screen', 'type' => 'customer_display', 'location_id' => $location->id, ]) ->assertRedirect(route('pos.devices.index')); $device = PosDevice::query()->where('owner_ref', $user->public_id)->firstOrFail(); $display = PosCustomerDisplay::query()->where('location_id', $location->id)->firstOrFail(); $this->assertNotEmpty($device->device_token); $this->assertSame($display->token, $device->device_token); } public function test_customer_display_device_requires_branch(): void { $user = $this->user(); $this->location($user); $this->actingAs($user) ->from(route('pos.devices.create')) ->post(route('pos.devices.store'), [ 'name' => 'Orphan screen', 'type' => 'customer_display', 'location_id' => '', ]) ->assertRedirect(route('pos.devices.create')) ->assertSessionHas('error'); } public function test_devices_index_lists_owned_devices(): void { $user = $this->user(); $location = $this->location($user); PosDevice::create([ 'owner_ref' => $user->public_id, 'location_id' => $location->id, 'name' => 'Kitchen pad', 'type' => 'tablet', 'status' => 'offline', ]); $this->actingAs($user) ->get(route('pos.devices.index')) ->assertOk() ->assertSee('Kitchen pad') ->assertSee('Devices'); } public function test_regenerate_token_updates_customer_display(): void { $user = $this->user(); $location = $this->location($user); $device = app(DeviceService::class)->register([ 'owner_ref' => $user->public_id, 'name' => 'Display', 'type' => 'customer_display', 'location_id' => $location->id, ]); $old = $device->device_token; $this->actingAs($user) ->post(route('pos.devices.regenerate-token', $device)) ->assertRedirect(); $device->refresh(); $display = PosCustomerDisplay::query()->where('location_id', $location->id)->firstOrFail(); $this->assertNotSame($old, $device->device_token); $this->assertSame($device->device_token, $display->token); } public function test_customer_display_poll_marks_device_online(): void { $user = $this->user(); $location = $this->location($user); $device = app(DeviceService::class)->register([ 'owner_ref' => $user->public_id, 'name' => 'Display', 'type' => 'customer_display', 'location_id' => $location->id, ]); $this->getJson(route('pos.customer-display.state', ['token' => $device->device_token])) ->assertOk(); $device->refresh(); $this->assertSame('online', $device->status); $this->assertNotNull($device->last_online_at); } public function test_kitchen_display_device_opens_public_url_without_auth(): void { $user = $this->user(); $location = $this->location($user); $device = app(DeviceService::class)->register([ 'owner_ref' => $user->public_id, 'name' => 'Pass tablet', 'type' => 'kitchen_display', 'location_id' => $location->id, ]); $this->assertNotEmpty($device->device_token); $this->get(route('pos.kitchen-display.show', ['token' => $device->device_token])) ->assertOk() ->assertSee('Kitchen display') ->assertSee('Main register'); $this->getJson(route('pos.kitchen-display.feed', ['token' => $device->device_token])) ->assertOk() ->assertJsonStructure(['tickets', 'server_time']); $device->refresh(); $this->assertSame('online', $device->status); } public function test_kitchen_display_bump_advances_line_state(): void { $user = $this->user(); $location = $this->location($user); $device = app(DeviceService::class)->register([ 'owner_ref' => $user->public_id, 'name' => 'KDS', 'type' => 'kitchen_display', 'location_id' => $location->id, ]); $sale = \App\Models\PosSale::create([ 'owner_ref' => $user->public_id, 'location_id' => $location->id, 'reference' => 'POS-KDS-1', 'currency' => 'GHS', 'status' => 'pending', 'payment_method' => 'cash', 'order_type' => 'dine_in', 'kitchen_status' => \App\Models\PosSale::KITCHEN_ACTIVE, 'kitchen_sent_at' => now(), 'total_minor' => 1000, 'subtotal_minor' => 1000, ]); $line = \App\Models\PosSaleLine::create([ 'pos_sale_id' => $sale->id, 'name' => 'Jollof', 'unit_price_minor' => 1000, 'quantity' => 1, 'line_total_minor' => 1000, 'position' => 0, 'kitchen_state' => \App\Models\PosSaleLine::KITCHEN_QUEUED, ]); $this->postJson(route('pos.kitchen-display.bump', [ 'token' => $device->device_token, 'line' => $line->id, ]))->assertOk()->assertJsonPath('state', 'preparing'); $this->assertSame('preparing', $line->fresh()->kitchen_state); } public function test_device_service_open_url_for_kitchen_is_public(): void { $user = $this->user(); $location = $this->location($user); $device = app(DeviceService::class)->register([ 'owner_ref' => $user->public_id, 'name' => 'KDS', 'type' => 'kitchen_display', 'location_id' => $location->id, ]); $url = app(DeviceService::class)->openUrl($device); $this->assertNotNull($url); $this->assertStringContainsString('/kitchen-display/', $url); $this->assertStringContainsString($device->device_token, $url); } }