getenv('REDIS_CONFIG_HOST') ?: getenv('REDIS_CONFIG_DEBUG_HOST') ?: 'redis', 'user' => getenv('REDIS_CONFIG_USER') ?: getenv('REDIS_CONFIG_DEBUG_USER') ?: 'default', 'database' => getenv('REDIS_CONFIG_DATABASE') ?: getenv('REDIS_CONFIG_DEBUG_DATABASE') ?: '0', 'password' => getenv('REDIS_CONFIG_PASSWORD') ?: getenv('REDIS_CONFIG_DEBUG_PASSWORD') ?: '', 'port' => getenv('REDIS_CONFIG_PORT') ?: getenv('REDIS_CONFIG_DEBUG_PORT') ?: '6379', ]; define('redis', (new \classes\redis())->connect()); } function selfserve_customer_start_make_available(int $laneId): void { selfserve_customer_start_ensure_legacy_redis_constant(); $lane = (new \classes\selfserve())->lane($laneId); $lane->setLaneStatus(\modules\selfserve\helpers\selfserve_lane_status::AVAILABLE); $lane->setLaneState(\modules\selfserve\helpers\selfserve_lane_state::IDLE); $lane->setCustomerNumber(0); $lane->setLicensePlate(''); $lane->setWashStartTime(0); } it('allows the customer self-serve start sequence without department access', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, ]); $headers = api_fixtures()->bearerHeaders( api_fixtures()->createAuthToken((int)$scenario['customer']['id']) ); $laneId = (int)$scenario['lane']['id']; $reg = (string)$scenario['vehicle']['reg']; selfserve_customer_start_make_available($laneId); api_client() ->get('/department/selfserve/vehicle/allowed?lane_id=' . $laneId . '®=' . urlencode($reg), $headers) ->assertStatus(200) ->assertSuccess(true); $summaryResponse = api_client() ->get('/department/selfserve/washes/summary?lane_id=' . $laneId . '®=' . urlencode($reg), $headers) ->assertStatus(200) ->assertSuccess(true); expect($summaryResponse->data()['session']['customer_number'] ?? null) ->toBe((int)$scenario['customer']['customer_number']); api_client() ->post('/modules/self-serve/lane/services/allowed', [ 'lane_id' => $laneId, 'task_ids' => [(int)$scenario['tasks'][1]['id']], ], $headers) ->assertStatus(200) ->assertSuccess(true); $startResponse = api_client() ->post('/modules/self-serve/lane/command', [ 'lane_id' => $laneId, 'command' => 'START', 'license_plate' => $reg, 'defer_relay_side_effects' => true, ], $headers) ->assertStatus(200) ->assertSuccess(true); expect($startResponse->data()['status'] ?? null)->toBe('OCCUPIED') ->and($startResponse->data()['customer_number'] ?? null) ->toBe((int)$scenario['customer']['customer_number']); }); it('marks the active customer session relay-enabled after machine relay enable', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', 'modules_selfserve_lane_relay_enable_machine', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, 'session' => [ 'status' => 'READY_FOR_MACHINE_START', 'machine_relay_enabled' => 0, 'machine_relay_enabled_at' => null, 'machine_start_triggered' => 0, 'machine_start_triggered_at' => null, 'wash_started_at' => null, ], ]); $headers = api_fixtures()->bearerHeaders( api_fixtures()->createAuthToken((int)$scenario['customer']['id']) ); $laneId = (int)$scenario['lane']['id']; $reg = (string)$scenario['vehicle']['reg']; $machineTaskId = (int)$scenario['tasks'][1]['id']; selfserve_customer_start_make_available($laneId); api_client() ->get('/department/selfserve/vehicle/allowed?lane_id=' . $laneId . '®=' . urlencode($reg), $headers) ->assertStatus(200) ->assertSuccess(true); api_client() ->post('/modules/self-serve/lane/services/allowed', [ 'lane_id' => $laneId, 'task_ids' => [$machineTaskId], ], $headers) ->assertStatus(200) ->assertSuccess(true); api_client() ->post('/modules/self-serve/lane/command', [ 'lane_id' => $laneId, 'command' => 'START', 'license_plate' => $reg, 'defer_relay_side_effects' => true, ], $headers) ->assertStatus(200) ->assertSuccess(true); api_client() ->post('/modules/self-serve/lane/relay/machine/enable', [ 'lane_id' => $laneId, ], $headers) ->assertStatus(200) ->assertSuccess(true); $activeResponse = api_client() ->get('/modules/self-serve/lane/wash/my-active-wash', $headers) ->assertStatus(200) ->assertSuccess(true); expect($activeResponse->data()['session']['status'] ?? null)->toBe('MACHINE_RELAY_ENABLED') ->and($activeResponse->data()['session']['machine_relay_enabled'] ?? null)->toBeTrue() ->and($activeResponse->data()['session']['machine_start_triggered'] ?? null)->toBeFalse(); }); it('derives allowed services from v2 session task snapshots when task rows are not legacy records', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, ]); $headers = api_fixtures()->bearerHeaders( api_fixtures()->createAuthToken((int)$scenario['customer']['id']) ); $laneId = (int)$scenario['lane']['id']; $v2TaskId = 900000 + $laneId; $sessionTask = (new \objects\selfserve_wash_session_tasks_o())->addSnapshot( (int)$scenario['session']['id'], $v2TaskId, 'V2 path editor machine task', 'Task exists only in the session snapshot.', ['MACHINE', 'PROGRAM_PICKER'], ['program_picker', 6, 'start'], (int)$scenario['product']['id'], ); try { $response = api_client() ->post('/modules/self-serve/lane/services/allowed', [ 'lane_id' => $laneId, 'task_ids' => [$v2TaskId], ], $headers) ->assertStatus(200) ->assertSuccess(true); expect($response->data()['allowed_services'] ?? [])->toBe([ 'MACHINE', 'PROGRAM_PICKER', ]); } finally { $sessionTask->delete(); } }); it('derives allowed services from published v2 config task snapshots when no session exists yet', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, 'session' => [ 'status' => 'COMPLETED', 'completed_at' => date('Y-m-d H:i:s'), 'wash_started_at' => date('Y-m-d H:i:s'), ], ]); $headers = api_fixtures()->bearerHeaders( api_fixtures()->createAuthToken((int)$scenario['customer']['id']) ); $laneId = (int)$scenario['lane']['id']; $departmentId = (int)$scenario['department']['id']; $productId = (int)$scenario['product']['id']; $v2TaskId = 910000 + $laneId; $configVersion = (new \objects\selfserve_config_versions_o())->add( $departmentId, \modules\selfserve\classes\selfserve_config_versioning::STATUS_PUBLISHED, 1, [ 'schema_version' => 2, 'tasks' => [ [ 'id' => $v2TaskId, 'department' => $departmentId, 'lane' => $laneId, 'product' => $productId, 'machine_type_id' => 0, 'task' => 'Published v2 machine task', 'description' => 'Task exists only in the published config snapshot.', 'order_priority' => 10, 'services' => ['MACHINE', 'PROGRAM_PICKER'], ], ], ], null, null, null, date('Y-m-d H:i:s'), ); try { $response = api_client() ->post('/modules/self-serve/lane/services/allowed', [ 'lane_id' => $laneId, 'task_ids' => [$v2TaskId], ], $headers) ->assertStatus(200) ->assertSuccess(true); expect($response->data()['allowed_services'] ?? [])->toBe([ 'MACHINE', 'PROGRAM_PICKER', ]); } finally { $configVersion->delete(); } }); it('keeps long generated task descriptions when refreshing vehicle eligibility snapshots', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, ]); $headers = api_fixtures()->bearerHeaders( api_fixtures()->createAuthToken((int)$scenario['customer']['id']) ); $laneId = (int)$scenario['lane']['id']; $reg = (string)$scenario['vehicle']['reg']; $productId = (int)$scenario['product']['id']; $taskId = (int)$scenario['tasks'][1]['id']; $longDescription = str_repeat('Hvis ja - vaelg tagboerste program (#6) og lift program (#2) / ', 6); $statement = api_test_runtime()->db()->prepare( 'UPDATE department_selfserve_tasks SET description = ? WHERE id = ?' ); $statement->bind_param('si', $longDescription, $taskId); $statement->execute(); api_client() ->get( '/department/selfserve/vehicle/allowed?lane_id=' . $laneId . '®=' . urlencode($reg) . '&vehicle_type=' . $productId, $headers ) ->assertStatus(200) ->assertSuccess(true); $sessionTask = api_test_runtime()->queryOne( 'SELECT description FROM selfserve_wash_session_tasks' . ' WHERE session_id = ' . (int)$scenario['session']['id'] . ' AND task_id = ' . $taskId . ' LIMIT 1' ); expect($sessionTask['description'] ?? null)->toBe($longDescription); }); it('does not create an active wash preview session from read-only eligibility checks', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, ]); $headers = api_fixtures()->bearerHeaders( api_fixtures()->createAuthToken((int)$scenario['customer']['id']) ); $laneId = (int)$scenario['lane']['id']; $reg = (string)$scenario['vehicle']['reg']; $productId = (int)$scenario['product']['id']; $sessionId = (int)$scenario['session']['id']; api_test_runtime()->db()->query( 'DELETE FROM selfserve_wash_session_events WHERE session_id = ' . $sessionId ); api_test_runtime()->db()->query( 'DELETE FROM selfserve_wash_session_tasks WHERE session_id = ' . $sessionId ); api_test_runtime()->db()->query( 'DELETE FROM selfserve_wash_session_answers WHERE session_id = ' . $sessionId ); api_test_runtime()->db()->query( 'DELETE FROM selfserve_wash_sessions WHERE id = ' . $sessionId ); selfserve_customer_start_make_available($laneId); api_client() ->get( '/department/selfserve/vehicle/allowed?lane_id=' . $laneId . '®=' . urlencode($reg) . '&vehicle_type=' . $productId, $headers ) ->assertStatus(200) ->assertSuccess(true); $openSession = api_test_runtime()->queryOne( 'SELECT id FROM selfserve_wash_sessions' . ' WHERE lane_id = ' . $laneId . ' AND reg = "' . api_test_runtime()->db()->real_escape_string($reg) . '"' . ' AND completed_at IS NULL' . ' AND deleted_at IS NULL' . ' LIMIT 1' ); expect($openSession)->toBeNull(); }); it('creates a durable wash session from customer start after read-only eligibility', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, ]); $headers = api_fixtures()->bearerHeaders( api_fixtures()->createAuthToken((int)$scenario['customer']['id']) ); $laneId = (int)$scenario['lane']['id']; $reg = (string)$scenario['vehicle']['reg']; $productId = (int)$scenario['product']['id']; $sessionId = (int)$scenario['session']['id']; api_test_runtime()->db()->query( 'DELETE FROM selfserve_wash_session_events WHERE session_id = ' . $sessionId ); api_test_runtime()->db()->query( 'DELETE FROM selfserve_wash_session_tasks WHERE session_id = ' . $sessionId ); api_test_runtime()->db()->query( 'DELETE FROM selfserve_wash_session_answers WHERE session_id = ' . $sessionId ); api_test_runtime()->db()->query( 'DELETE FROM selfserve_wash_sessions WHERE id = ' . $sessionId ); selfserve_customer_start_make_available($laneId); api_client() ->get( '/department/selfserve/vehicle/allowed?lane_id=' . $laneId . '®=' . urlencode($reg) . '&vehicle_type=' . $productId, $headers ) ->assertStatus(200) ->assertSuccess(true); api_client() ->post('/modules/self-serve/lane/command', [ 'lane_id' => $laneId, 'command' => 'START', 'license_plate' => $reg, 'defer_relay_side_effects' => true, ], $headers) ->assertStatus(200) ->assertSuccess(true); $openSession = api_test_runtime()->queryOne( 'SELECT id, status, customer_number FROM selfserve_wash_sessions' . ' WHERE lane_id = ' . $laneId . ' AND reg = "' . api_test_runtime()->db()->real_escape_string($reg) . '"' . ' AND completed_at IS NULL' . ' AND deleted_at IS NULL' . ' ORDER BY id DESC LIMIT 1' ); expect($openSession)->not->toBeNull() ->and((int)$openSession['customer_number'])->toBe((int)$scenario['customer']['customer_number']) ->and($openSession['status'])->toBe('READY_FOR_MACHINE_START'); selfserve_customer_start_make_available($laneId); }); it('refreshes an active wash summary with vehicle type without a namespace error', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, 'session' => [ 'status' => 'MACHINE_RELAY_ENABLED', 'machine_relay_enabled' => 1, 'machine_relay_enabled_at' => date('Y-m-d H:i:s'), 'completed_at' => null, ], ]); $headers = api_fixtures()->bearerHeaders( api_fixtures()->createAuthToken((int)$scenario['customer']['id']) ); $sessionId = (int)$scenario['session']['id']; $productId = (int)$scenario['product']['id']; $summaryResponse = api_client() ->get( '/department/selfserve/washes/summary?session_id=' . $sessionId . '&vehicle_type=' . $productId, $headers ) ->assertStatus(200) ->assertSuccess(true); expect($summaryResponse->data()['session']['id'] ?? null)->toBe($sessionId) ->and($summaryResponse->data()['session']['status'] ?? null)->toBe('MACHINE_RELAY_ENABLED'); }); it('does not create a replacement session when refreshing a completed summary with vehicle type', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, 'session' => [ 'status' => 'COMPLETED', 'completed_at' => date('Y-m-d H:i:s'), ], ]); $headers = api_fixtures()->bearerHeaders( api_fixtures()->createAuthToken((int)$scenario['customer']['id']) ); $laneId = (int)$scenario['lane']['id']; $reg = (string)$scenario['vehicle']['reg']; $productId = (int)$scenario['product']['id']; $sessionId = (int)$scenario['session']['id']; selfserve_customer_start_make_available($laneId); $summaryResponse = api_client() ->get( '/department/selfserve/washes/summary?lane_id=' . $laneId . '®=' . urlencode($reg) . '&vehicle_type=' . $productId, $headers ) ->assertStatus(200) ->assertSuccess(true); expect($summaryResponse->data()['session']['id'] ?? null)->toBe($sessionId) ->and($summaryResponse->data()['session']['status'] ?? null)->toBe('COMPLETED'); $openSession = api_test_runtime()->queryOne( 'SELECT id FROM selfserve_wash_sessions' . ' WHERE lane_id = ' . $laneId . ' AND reg = "' . api_test_runtime()->db()->real_escape_string($reg) . '"' . ' AND completed_at IS NULL' . ' AND deleted_at IS NULL' . ' LIMIT 1' ); expect($openSession)->toBeNull(); });