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_lane_command_lane(int $laneId): \modules\selfserve\classes\selfserve_lane { selfserve_lane_command_ensure_legacy_redis_constant(); return (new \classes\selfserve())->lane($laneId); } function selfserve_lane_command_make_available(int $laneId): void { $lane = selfserve_lane_command_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); } function selfserve_lane_command_make_occupied(int $laneId, int $customerNumber, string $licensePlate): void { $lane = selfserve_lane_command_lane($laneId); $lane->setLaneStatus(\modules\selfserve\helpers\selfserve_lane_status::OCCUPIED); $lane->setLaneState(\modules\selfserve\helpers\selfserve_lane_state::IN_WASH); $lane->setCustomerNumber($customerNumber); $lane->setLicensePlate($licensePlate); $lane->setWashStartTime(time() - 60); } it('allows customer self-serve permission to execute START without department access when department and lane are enabled', 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, ]); selfserve_lane_command_make_available((int)$scenario['lane']['id']); $token = api_fixtures()->createAuthToken((int)$scenario['customer']['id']); $response = api_client()->post('/modules/self-serve/lane/command', [ 'lane_id' => (int)$scenario['lane']['id'], 'command' => 'START', 'license_plate' => (string)$scenario['vehicle']['reg'], 'wash_type' => 'Manual', 'defer_relay_side_effects' => true, ], api_fixtures()->bearerHeaders($token)); $response ->assertStatus(200) ->assertSuccess(true); expect($response->data()['status'] ?? null)->toBe('OCCUPIED') ->and($response->data()['customer_number'] ?? null)->toBe((int)$scenario['customer']['customer_number']); }); it('denies customer START when department self-serve is disabled', function (): void { $group = api_fixtures()->createGroup([], [ 'list_own_department_selfserve_vehicle_conditions', ]); $scenario = api_fixtures()->createSelfServeScenario([ 'customer' => ['group_id' => $group['id']], 'department_selfserve_enabled' => false, 'lane_selfserve_enabled' => true, ]); selfserve_lane_command_make_available((int)$scenario['lane']['id']); $token = api_fixtures()->createAuthToken((int)$scenario['customer']['id']); $response = api_client()->post('/modules/self-serve/lane/command', [ 'lane_id' => (int)$scenario['lane']['id'], 'command' => 'START', 'license_plate' => (string)$scenario['vehicle']['reg'], ], api_fixtures()->bearerHeaders($token)); $response ->assertStatus(403) ->assertMissingPermissions([ 'department_access_' . (int)$scenario['department']['id'], 'list_own_department_selfserve_vehicle_conditions', ]); }); it('denies customer START when lane self-serve is disabled', 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' => false, ]); selfserve_lane_command_make_available((int)$scenario['lane']['id']); $token = api_fixtures()->createAuthToken((int)$scenario['customer']['id']); $response = api_client()->post('/modules/self-serve/lane/command', [ 'lane_id' => (int)$scenario['lane']['id'], 'command' => 'START', 'license_plate' => (string)$scenario['vehicle']['reg'], ], api_fixtures()->bearerHeaders($token)); $response ->assertStatus(403) ->assertMissingPermissions([ 'department_access_' . (int)$scenario['department']['id'], 'list_own_department_selfserve_vehicle_conditions', ]); }); it('denies a customer STOP for another customers active lane', function (): void { $scenario = api_fixtures()->createSelfServeScenario([ 'department_selfserve_enabled' => true, 'lane_selfserve_enabled' => true, ]); selfserve_lane_command_make_occupied( (int)$scenario['lane']['id'], (int)$scenario['customer']['customer_number'], (string)$scenario['vehicle']['reg'] ); $otherSession = api_fixtures()->createUserSession([ 'list_own_department_selfserve_vehicle_conditions', ]); $response = api_client()->post('/modules/self-serve/lane/command', [ 'lane_id' => (int)$scenario['lane']['id'], 'command' => 'STOP', ], $otherSession['headers']); $response ->assertStatus(403) ->assertMissingPermissions([ 'department_access_' . (int)$scenario['department']['id'], 'list_own_department_selfserve_vehicle_conditions', ]); }); it('still allows elevated operators with department access to execute lane commands', function (): void { $scenario = api_fixtures()->createSelfServeScenario([ 'department_selfserve_enabled' => false, 'lane_selfserve_enabled' => false, ]); selfserve_lane_command_make_available((int)$scenario['lane']['id']); $session = api_fixtures()->createUserSession([ 'department_access_' . (int)$scenario['department']['id'], 'modules_selfserve_lane_command_execute', 'modules_selfserve_lane_command_execute_start', ]); $response = api_client()->post('/modules/self-serve/lane/command', [ 'lane_id' => (int)$scenario['lane']['id'], 'command' => 'START', 'license_plate' => 'OP' . (int)$scenario['lane']['id'], 'wash_type' => 'Manual', 'defer_relay_side_effects' => true, ], $session['headers']); $response ->assertStatus(200) ->assertSuccess(true); expect($response->data()['status'] ?? null)->toBe('OCCUPIED'); }); it('keeps operator-only commands elevated-only for customers and reports command permissions', 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, ]); selfserve_lane_command_make_available((int)$scenario['lane']['id']); $token = api_fixtures()->createAuthToken((int)$scenario['customer']['id']); $response = api_client()->post('/modules/self-serve/lane/command', [ 'lane_id' => (int)$scenario['lane']['id'], 'command' => 'RESET', ], api_fixtures()->bearerHeaders($token)); $response ->assertStatus(403) ->assertMissingPermissions([ 'department_access_' . (int)$scenario['department']['id'], ]); });