345 lines
12 KiB
PHP
345 lines
12 KiB
PHP
<?php
|
|
|
|
app_require('modules/selfserve/traits/selfserve_lane_command_t.php');
|
|
app_require('modules/selfserve/helpers/selfserve_lane_command.php');
|
|
app_require('modules/selfserve/helpers/selfserve_lane_mode.php');
|
|
app_require('modules/selfserve/helpers/selfserve_lane_port.php');
|
|
app_require('modules/selfserve/helpers/selfserve_lane_relay.php');
|
|
app_require('modules/selfserve/helpers/selfserve_lane_state.php');
|
|
app_require('modules/selfserve/helpers/selfserve_lane_status.php');
|
|
app_require('modules/selfserve/classes/selfserve_lane_command_arguments.php');
|
|
|
|
use modules\selfserve\classes\selfserve_lane_command_arguments;
|
|
use modules\selfserve\helpers\selfserve_lane_command;
|
|
use modules\selfserve\helpers\selfserve_lane_mode;
|
|
use modules\selfserve\helpers\selfserve_lane_port;
|
|
use modules\selfserve\helpers\selfserve_lane_relay;
|
|
use modules\selfserve\helpers\selfserve_lane_state;
|
|
use modules\selfserve\helpers\selfserve_lane_status;
|
|
use modules\selfserve\traits\selfserve_lane_command_t;
|
|
|
|
class SelfserveLaneCommandValueFake
|
|
{
|
|
public function __construct(private readonly string $value) {}
|
|
|
|
public function value(): string
|
|
{
|
|
return $this->value;
|
|
}
|
|
}
|
|
|
|
class SelfserveLaneCommandDepartmentLaneFake
|
|
{
|
|
public SelfserveLaneCommandValueFake $relay_machine_id;
|
|
public SelfserveLaneCommandValueFake $relay_machine_program_picker_id;
|
|
public SelfserveLaneCommandValueFake $relay_machine_cleaner_id;
|
|
|
|
public function __construct(string $machineRelayId, string $programRelayId, string $cleanerRelayId)
|
|
{
|
|
$this->relay_machine_id = new SelfserveLaneCommandValueFake($machineRelayId);
|
|
$this->relay_machine_program_picker_id = new SelfserveLaneCommandValueFake($programRelayId);
|
|
$this->relay_machine_cleaner_id = new SelfserveLaneCommandValueFake($cleanerRelayId);
|
|
}
|
|
}
|
|
|
|
class SelfserveLaneStopFlowHarness
|
|
{
|
|
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 = 77;
|
|
public object $department_lane;
|
|
public int $invoiceCalls = 0;
|
|
public int $ensureInvoiceOrderContextCalls = 0;
|
|
public int $vehicleTypeProductAddCalls = 0;
|
|
public int $programSelectorStatusReads = 0;
|
|
public ?bool $lastVehicleTypeProductDecision = null;
|
|
public ?\Throwable $openThrowable = null;
|
|
public ?\Throwable $reportedStopTimeout = null;
|
|
/** @var selfserve_lane_port[] */
|
|
public array $openedPorts = [];
|
|
/** @var selfserve_lane_relay[] */
|
|
public array $turnedOffRelays = [];
|
|
/** @var array<int,string> */
|
|
public array $events = [];
|
|
|
|
private selfserve_lane_status $laneStatus;
|
|
private selfserve_lane_mode $laneMode;
|
|
private selfserve_lane_state $laneState;
|
|
private int $customerNumber = 1234;
|
|
private string $licensePlate = 'AB12345';
|
|
private int $washStartTime = 120;
|
|
private ?int $reservationStartTime = null;
|
|
private bool $bypassCustomerValidation = false;
|
|
private bool $programSelectorOnline;
|
|
private bool $programSelectorOn;
|
|
private bool $machineStartTriggered;
|
|
|
|
public function __construct(
|
|
bool $machineStartTriggered,
|
|
bool $programSelectorOnline = false,
|
|
bool $programSelectorOn = true,
|
|
string $machineRelayId = 'relay-machine',
|
|
string $programRelayId = 'relay-program',
|
|
string $cleanerRelayId = 'relay-cleaner'
|
|
) {
|
|
$this->machineStartTriggered = $machineStartTriggered;
|
|
$this->programSelectorOnline = $programSelectorOnline;
|
|
$this->programSelectorOn = $programSelectorOn;
|
|
$this->department_lane = new SelfserveLaneCommandDepartmentLaneFake(
|
|
$machineRelayId,
|
|
$programRelayId,
|
|
$cleanerRelayId
|
|
);
|
|
$this->laneStatus = selfserve_lane_status::OCCUPIED;
|
|
$this->laneMode = selfserve_lane_mode::MANUAL;
|
|
$this->laneState = selfserve_lane_state::IN_WASH;
|
|
}
|
|
|
|
public function getLaneStatus(): selfserve_lane_status
|
|
{
|
|
return $this->laneStatus;
|
|
}
|
|
|
|
public function setLaneStatus(selfserve_lane_status $status): void
|
|
{
|
|
$this->laneStatus = $status;
|
|
}
|
|
|
|
public function setLaneMode(selfserve_lane_mode $mode): void
|
|
{
|
|
$this->laneMode = $mode;
|
|
}
|
|
|
|
public function setLaneState(selfserve_lane_state $state): void
|
|
{
|
|
$this->laneState = $state;
|
|
}
|
|
|
|
public function setWashStartTime(?int $time): void
|
|
{
|
|
$this->washStartTime = $time ?? self::DEFAULT_WASH_START_TIME;
|
|
}
|
|
|
|
public function setCustomerNumber(?int $customerNumber): void
|
|
{
|
|
$this->customerNumber = $customerNumber ?? self::DEFAULT_CUSTOMER_NUMBER;
|
|
}
|
|
|
|
public function getCustomerNumber(): int
|
|
{
|
|
return $this->customerNumber;
|
|
}
|
|
|
|
public function setLicensePlate(?string $licensePlate): void
|
|
{
|
|
$this->licensePlate = $licensePlate ?? self::DEFAULT_LICENSE_PLATE;
|
|
}
|
|
|
|
public function getLicensePlate(): string
|
|
{
|
|
return $this->licensePlate;
|
|
}
|
|
|
|
public function setReservationStartTime(?int $reservationStartTime): void
|
|
{
|
|
$this->reservationStartTime = $reservationStartTime;
|
|
}
|
|
|
|
public function isBypassCustomerNumberValidation(): bool
|
|
{
|
|
return $this->bypassCustomerValidation;
|
|
}
|
|
|
|
public function invoice(): bool
|
|
{
|
|
$this->invoiceCalls++;
|
|
return true;
|
|
}
|
|
|
|
public function ensureInvoiceOrderContextForVehicleProduct(): bool
|
|
{
|
|
$this->ensureInvoiceOrderContextCalls++;
|
|
return true;
|
|
}
|
|
|
|
public function addVehicleTypeProductToLastInvoiceOrder(): bool
|
|
{
|
|
$this->vehicleTypeProductAddCalls++;
|
|
return true;
|
|
}
|
|
|
|
public function getMachineProgramPickerRelayStatus(): array
|
|
{
|
|
$this->programSelectorStatusReads++;
|
|
return [
|
|
'online' => $this->programSelectorOnline,
|
|
'on' => $this->programSelectorOn,
|
|
];
|
|
}
|
|
|
|
public function open(selfserve_lane_port $port): bool
|
|
{
|
|
$this->openedPorts[] = $port;
|
|
$this->events[] = 'open:' . $port->name;
|
|
if ($this->openThrowable !== null) {
|
|
throw $this->openThrowable;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function turnOffRelay(selfserve_lane_relay $relay): bool
|
|
{
|
|
$this->turnedOffRelays[] = $relay;
|
|
$this->events[] = 'relay:' . $relay->name . ':off';
|
|
return true;
|
|
}
|
|
|
|
public function setRelayStatusHard(selfserve_lane_relay $relay, bool $on, ?int $toggle_after = null): bool
|
|
{
|
|
if ($on === false) {
|
|
$this->turnedOffRelays[] = $relay;
|
|
$this->events[] = 'relay:' . $relay->name . ':off';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function logLaneAction(...$args): void {}
|
|
|
|
protected function completeLatestSessionForStop(): void
|
|
{
|
|
// No-op in unit tests.
|
|
}
|
|
|
|
protected function hasMachineStartSignalForStop(): bool
|
|
{
|
|
return $this->machineStartTriggered;
|
|
}
|
|
|
|
protected function addVehicleTypeProductToInvoiceIfNeeded(bool $should_add): void
|
|
{
|
|
$this->lastVehicleTypeProductDecision = $should_add;
|
|
if ($should_add) {
|
|
$this->vehicleTypeProductAddCalls++;
|
|
}
|
|
}
|
|
|
|
protected function reportWashStopExitTimeout(\Throwable $e): void
|
|
{
|
|
$this->reportedStopTimeout = $e;
|
|
}
|
|
}
|
|
|
|
it('adds vehicle type product on STOP when the physical machine ON signal was recorded, after turning relays off before exit', function (): void {
|
|
$lane = new SelfserveLaneStopFlowHarness(machineStartTriggered: true);
|
|
$args = (new selfserve_lane_command_arguments())->setCustomerNumber(1234);
|
|
|
|
$lane->execute(selfserve_lane_command::STOP, $args);
|
|
|
|
expect($lane->invoiceCalls)->toBe(1);
|
|
expect($lane->ensureInvoiceOrderContextCalls)->toBe(0);
|
|
expect($lane->vehicleTypeProductAddCalls)->toBe(1);
|
|
expect($lane->lastVehicleTypeProductDecision)->toBeTrue();
|
|
expect($lane->programSelectorStatusReads)->toBe(0);
|
|
expect($lane->openedPorts)->toBe([selfserve_lane_port::EXIT]);
|
|
expect($lane->turnedOffRelays)->toBe([
|
|
selfserve_lane_relay::MACHINE_CLEANER,
|
|
selfserve_lane_relay::MACHINE_PROGRAM_PICKER,
|
|
selfserve_lane_relay::MACHINE,
|
|
]);
|
|
expect($lane->events)->toBe([
|
|
'relay:MACHINE_CLEANER:off',
|
|
'relay:MACHINE_PROGRAM_PICKER:off',
|
|
'relay:MACHINE:off',
|
|
'open:EXIT',
|
|
]);
|
|
expect($lane->getLaneStatus())->toBe(selfserve_lane_status::AVAILABLE);
|
|
});
|
|
|
|
it('skips vehicle type product add when no physical machine ON signal was recorded and only disables configured relays', function (): void {
|
|
$lane = new SelfserveLaneStopFlowHarness(
|
|
machineStartTriggered: false,
|
|
programSelectorOnline: false,
|
|
machineRelayId: 'relay-machine',
|
|
programRelayId: 'relay-program',
|
|
cleanerRelayId: ''
|
|
);
|
|
$args = (new selfserve_lane_command_arguments())->setCustomerNumber(1234);
|
|
|
|
$lane->execute(selfserve_lane_command::STOP, $args);
|
|
|
|
expect($lane->invoiceCalls)->toBe(1);
|
|
expect($lane->ensureInvoiceOrderContextCalls)->toBe(0);
|
|
expect($lane->vehicleTypeProductAddCalls)->toBe(0);
|
|
expect($lane->lastVehicleTypeProductDecision)->toBeFalse();
|
|
expect($lane->programSelectorStatusReads)->toBe(0);
|
|
expect($lane->turnedOffRelays)->toBe([
|
|
selfserve_lane_relay::MACHINE_PROGRAM_PICKER,
|
|
selfserve_lane_relay::MACHINE,
|
|
]);
|
|
expect($lane->events)->toBe([
|
|
'relay:MACHINE_PROGRAM_PICKER:off',
|
|
'relay:MACHINE:off',
|
|
'open:EXIT',
|
|
]);
|
|
});
|
|
|
|
it('does not use selector relay online status as machine-wash billing evidence', function (): void {
|
|
$lane = new SelfserveLaneStopFlowHarness(
|
|
machineStartTriggered: false,
|
|
programSelectorOnline: true,
|
|
programSelectorOn: true
|
|
);
|
|
$args = (new selfserve_lane_command_arguments())->setCustomerNumber(1234);
|
|
|
|
$lane->execute(selfserve_lane_command::STOP, $args);
|
|
|
|
expect($lane->invoiceCalls)->toBe(1);
|
|
expect($lane->ensureInvoiceOrderContextCalls)->toBe(0);
|
|
expect($lane->vehicleTypeProductAddCalls)->toBe(0);
|
|
expect($lane->lastVehicleTypeProductDecision)->toBeFalse();
|
|
expect($lane->programSelectorStatusReads)->toBe(0);
|
|
});
|
|
|
|
it('continues STOP when exit relay dispatch times out ambiguously', function (): void {
|
|
$lane = new SelfserveLaneStopFlowHarness(machineStartTriggered: true);
|
|
$timeout = new \RuntimeException('Edge gateway command timed out');
|
|
$lane->openThrowable = $timeout;
|
|
$args = (new selfserve_lane_command_arguments())->setCustomerNumber(1234);
|
|
|
|
$lane->execute(selfserve_lane_command::STOP, $args);
|
|
|
|
expect($lane->openedPorts)->toBe([selfserve_lane_port::EXIT]);
|
|
expect($lane->reportedStopTimeout)->toBe($timeout);
|
|
expect($lane->invoiceCalls)->toBe(1);
|
|
expect($lane->vehicleTypeProductAddCalls)->toBe(1);
|
|
expect($lane->turnedOffRelays)->toBe([
|
|
selfserve_lane_relay::MACHINE_CLEANER,
|
|
selfserve_lane_relay::MACHINE_PROGRAM_PICKER,
|
|
selfserve_lane_relay::MACHINE,
|
|
]);
|
|
expect($lane->events)->toBe([
|
|
'relay:MACHINE_CLEANER:off',
|
|
'relay:MACHINE_PROGRAM_PICKER:off',
|
|
'relay:MACHINE:off',
|
|
'open:EXIT',
|
|
]);
|
|
expect($lane->getLaneStatus())->toBe(selfserve_lane_status::AVAILABLE);
|
|
});
|
|
|
|
it('still fails STOP for non-timeout exit relay errors', function (): void {
|
|
$lane = new SelfserveLaneStopFlowHarness(machineStartTriggered: true);
|
|
$lane->openThrowable = new \RuntimeException('Invalid relay ID for port EXIT');
|
|
$args = (new selfserve_lane_command_arguments())->setCustomerNumber(1234);
|
|
|
|
expect(fn() => $lane->execute(selfserve_lane_command::STOP, $args))
|
|
->toThrow(\RuntimeException::class, 'Invalid relay ID');
|
|
expect($lane->reportedStopTimeout)->toBeNull();
|
|
expect($lane->invoiceCalls)->toBe(0);
|
|
expect($lane->getLaneStatus())->toBe(selfserve_lane_status::OCCUPIED);
|
|
});
|