value; } } class SelfserveLanePropertyGateDepartmentLaneFake { public SelfserveLanePropertyGateValueFake $department; public function __construct(int $departmentId) { $this->department = new SelfserveLanePropertyGateValueFake($departmentId); } } class SelfserveLanePropertyGateFake extends department_gates_o { public int $openCalls = 0; public function __construct( private readonly bool $existsFlag = true, private readonly bool $throwOnOpen = false, private readonly string $throwMessage = 'gate open failed', ) {} public function exists(): bool { return $this->existsFlag; } public function openGate(): void { $this->openCalls++; if ($this->throwOnOpen) { throw new \RuntimeException($this->throwMessage); } } } class SelfserveLanePropertyGateHarness { use selfserve_lane_command_t; public const DEFAULT_WASH_START_TIME = 0; public const DEFAULT_CUSTOMER_NUMBER = 0; public const DEFAULT_LICENSE_PLATE = ''; public int $id = 901; public object $department_lane; public bool $selfServeEnabled = true; public ?department_gates_o $entranceGate = null; public ?department_gates_o $exitGate = null; public function __construct(int $departmentId = 77) { $this->department_lane = new SelfserveLanePropertyGateDepartmentLaneFake($departmentId); } protected function isDepartmentSelfServeEnabled(): bool { return $this->selfServeEnabled; } protected function resolveDepartmentGateForCommand(int $department_id, bool $isAccessGate): ?department_gates_o { return $isAccessGate ? $this->entranceGate : $this->exitGate; } } it('opens entrance gate for OPEN_PROPERTY_ACCESS_GATE command', function (): void { $lane = new SelfserveLanePropertyGateHarness(); $gate = new SelfserveLanePropertyGateFake(); $lane->entranceGate = $gate; $lane->execute(selfserve_lane_command::OPEN_PROPERTY_ACCESS_GATE, new selfserve_lane_command_arguments()); expect($gate->openCalls)->toBe(1); }); it('opens exit gate for OPEN_PROPERTY_EXIT_GATE command', function (): void { $lane = new SelfserveLanePropertyGateHarness(); $gate = new SelfserveLanePropertyGateFake(); $lane->exitGate = $gate; $lane->execute(selfserve_lane_command::OPEN_PROPERTY_EXIT_GATE, new selfserve_lane_command_arguments()); expect($gate->openCalls)->toBe(1); }); it('blocks property gate commands when department self-serve is disabled', function (): void { $lane = new SelfserveLanePropertyGateHarness(); $lane->selfServeEnabled = false; $lane->entranceGate = new SelfserveLanePropertyGateFake(); expect(function () use ($lane): void { $lane->execute(selfserve_lane_command::OPEN_PROPERTY_ACCESS_GATE, new selfserve_lane_command_arguments()); })->toThrow(\RuntimeException::class, 'Self-serve is not enabled'); }); it('throws a clear error when entrance gate is missing for property access command', function (): void { $lane = new SelfserveLanePropertyGateHarness(); $lane->entranceGate = null; expect(function () use ($lane): void { $lane->execute(selfserve_lane_command::OPEN_PROPERTY_ACCESS_GATE, new selfserve_lane_command_arguments()); })->toThrow(\RuntimeException::class, 'No entrance gate configured'); }); it('wraps low-level gate errors for property access command failures', function (): void { $lane = new SelfserveLanePropertyGateHarness(); $lane->entranceGate = new SelfserveLanePropertyGateFake( existsFlag: true, throwOnOpen: true, throwMessage: 'simulated gateway timeout', ); expect(function () use ($lane): void { $lane->execute(selfserve_lane_command::OPEN_PROPERTY_ACCESS_GATE, new selfserve_lane_command_arguments()); })->toThrow(\RuntimeException::class, 'Failed to open property access gate.'); }); it('wraps low-level gate errors for property exit command failures', function (): void { $lane = new SelfserveLanePropertyGateHarness(); $lane->exitGate = new SelfserveLanePropertyGateFake( existsFlag: true, throwOnOpen: true, throwMessage: 'simulated provider error', ); expect(function () use ($lane): void { $lane->execute(selfserve_lane_command::OPEN_PROPERTY_EXIT_GATE, new selfserve_lane_command_arguments()); })->toThrow(\RuntimeException::class, 'Failed to open property exit gate.'); });