Add fake classes for relay logic and refactor relay shutdown without pre-checking status

Introduce helper classes `SelfserveWashCompletionRelayValueFake`, `SelfserveWashCompletionDepartmentLaneFake`, `SelfserveWashCompletionRelayLaneFake`, and `SelfserveWashCompletionFlowHarness` to simulate relay logic for unit tests. Refactor `turnOffRelayIfConfiguredAndOn` to `turnOffRelayIfConfigured`, removing relay status pre-check for cleaner and machine relays when completing a wash session, and test associated relay actions.
This commit is contained in:
Jeppe Bundgaard
2026-05-27 19:30:31 +02:00
parent eef436d44b
commit 54de2e5674
3 changed files with 104 additions and 21 deletions
@@ -273,28 +273,19 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
return;
}
$this->turnOffRelayIfConfiguredAndOn($lane, selfserve_lane_relay::MACHINE);
$this->turnOffRelayIfConfiguredAndOn($lane, selfserve_lane_relay::MACHINE_CLEANER);
$this->turnOffRelayIfConfigured($lane, selfserve_lane_relay::MACHINE);
$this->turnOffRelayIfConfigured($lane, selfserve_lane_relay::MACHINE_CLEANER);
} catch (\Throwable) {
// Best effort only; session completion flow must continue.
}
}
protected function turnOffRelayIfConfiguredAndOn(selfserve_lane $lane, selfserve_lane_relay $relay): void
protected function turnOffRelayIfConfigured(selfserve_lane $lane, selfserve_lane_relay $relay): void
{
if (!$this->isRelayConfiguredForLane($lane, $relay)) {
return;
}
try {
$status = $lane->getRelayStatus($relay);
if ((bool)($status['on'] ?? false) !== true) {
return;
}
} catch (\Throwable) {
// If relay status can't be read, still attempt turn-off as best effort.
}
try {
$lane->setRelayStatusHard($relay, false);
} catch (\Throwable) {
@@ -723,14 +714,14 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
protected function enableMachineRelayIfAllowed(array $snapshot, selfserve_wash_sessions_o $session): void
{
if ((bool)$session->machine_relay_enabled->value() === true) {
return;
}
$laneId = (int)$snapshot['lane']['id'];
$lane = (new selfserve())->lane($laneId);
$this->enableCleanerRelayForStartedWash($lane);
if ((bool)$session->machine_relay_enabled->value() === true) {
return;
}
$session->markRelayEnabled();
$this->logSessionEvent((int)$session->id, selfserve_wash_event_type::MACHINE_RELAY_ENABLED, [
'lane_id' => $laneId,
@@ -22,6 +22,11 @@ it('keeps cleaner relay enable wired into machine relay start paths', function (
expect($enableMachineRelayMethodOffset)->not->toBeFalse();
$enableMachineRelayMethod = substr($washFlow, (int)$enableMachineRelayMethodOffset, 1200);
expect($enableMachineRelayMethod)->toContain('$this->enableCleanerRelayForStartedWash($lane);');
$cleanerEnableOffset = strpos($enableMachineRelayMethod, '$this->enableCleanerRelayForStartedWash($lane);');
$alreadyEnabledGuardOffset = strpos($enableMachineRelayMethod, 'if ((bool)$session->machine_relay_enabled->value() === true)');
expect($cleanerEnableOffset)->not->toBeFalse()
->and($alreadyEnabledGuardOffset)->not->toBeFalse()
->and($cleanerEnableOffset)->toBeLessThan($alreadyEnabledGuardOffset);
expect($moduleRoute)->not->toBeFalse();
$machineEnableRouteOffset = strpos($moduleRoute, '/modules/self-serve/lane/relay/machine/enable');
@@ -1,5 +1,79 @@
<?php
app_require('classes/object_property.php');
app_require('objects/department_lanes_o.php');
app_require('modules/selfserve/classes/selfserve_lane.php');
app_require('modules/selfserve/classes/selfserve_wash_flow.php');
app_require('modules/selfserve/helpers/selfserve_lane_relay.php');
use modules\selfserve\classes\selfserve_lane;
use modules\selfserve\classes\selfserve_wash_flow;
use modules\selfserve\helpers\selfserve_lane_relay;
class SelfserveWashCompletionRelayValueFake extends \classes\object_property
{
public function __construct(private readonly mixed $storedValue) {}
public function value(): mixed
{
return $this->storedValue;
}
}
class SelfserveWashCompletionDepartmentLaneFake extends \objects\department_lanes_o
{
public function __construct(string $machineRelayId = 'relay-machine', string $cleanerRelayId = 'relay-cleaner')
{
$this->relay_machine_id = new SelfserveWashCompletionRelayValueFake($machineRelayId);
$this->relay_machine_cleaner_id = new SelfserveWashCompletionRelayValueFake($cleanerRelayId);
$this->relay_machine_program_picker_id = new SelfserveWashCompletionRelayValueFake('');
}
public function exists(): bool
{
return true;
}
}
class SelfserveWashCompletionRelayLaneFake extends selfserve_lane
{
/** @var selfserve_lane_relay[] */
public array $statusReads = [];
/** @var array<int,array{0:selfserve_lane_relay,1:bool}> */
public array $relayWrites = [];
public function __construct(bool $reportedOn)
{
$this->id = 77;
$this->reportedOn = $reportedOn;
$this->department_lane = new SelfserveWashCompletionDepartmentLaneFake();
}
private bool $reportedOn;
public function getRelayStatus(selfserve_lane_relay $relay): array
{
$this->statusReads[] = $relay;
return ['on' => $this->reportedOn];
}
public function setRelayStatusHard(selfserve_lane_relay $relay, bool $on): bool
{
$this->relayWrites[] = [$relay, $on];
return true;
}
}
class SelfserveWashCompletionFlowHarness extends selfserve_wash_flow
{
public function __construct() {}
public function turnOffConfiguredRelay(selfserve_lane $lane, selfserve_lane_relay $relay): void
{
$this->turnOffRelayIfConfigured($lane, $relay);
}
}
it('forces machine and cleaner relays off when a self-serve wash session is completed', function (): void {
$washFlow = file_get_contents(app_path('modules/selfserve/classes/selfserve_wash_flow.php'));
@@ -10,14 +84,27 @@ it('forces machine and cleaner relays off when a self-serve wash session is comp
expect($methodOffset)->not->toBeFalse();
$methodBody = substr($washFlow, (int)$methodOffset, 1500);
expect($methodBody)->toContain('$this->turnOffRelayIfConfiguredAndOn($lane, selfserve_lane_relay::MACHINE);');
expect($methodBody)->toContain('$this->turnOffRelayIfConfiguredAndOn($lane, selfserve_lane_relay::MACHINE_CLEANER);');
expect($methodBody)->toContain('$this->turnOffRelayIfConfigured($lane, selfserve_lane_relay::MACHINE);');
expect($methodBody)->toContain('$this->turnOffRelayIfConfigured($lane, selfserve_lane_relay::MACHINE_CLEANER);');
$helperOffset = strpos($washFlow, 'protected function turnOffRelayIfConfiguredAndOn');
$helperOffset = strpos($washFlow, 'protected function turnOffRelayIfConfigured');
expect($helperOffset)->not->toBeFalse();
$helperBody = substr($washFlow, (int)$helperOffset, 1500);
expect($helperBody)->toContain('$status = $lane->getRelayStatus($relay);');
expect($helperBody)->toContain("if ((bool)(\$status['on'] ?? false) !== true)");
expect($helperBody)->toContain('$lane->setRelayStatusHard($relay, false);');
expect($helperBody)->not->toContain('$lane->getRelayStatus($relay)');
});
it('always dispatches completion relay off for configured machine relays without a status precheck', function (): void {
$lane = new SelfserveWashCompletionRelayLaneFake(reportedOn: false);
$flow = new SelfserveWashCompletionFlowHarness();
$flow->turnOffConfiguredRelay($lane, selfserve_lane_relay::MACHINE);
$flow->turnOffConfiguredRelay($lane, selfserve_lane_relay::MACHINE_CLEANER);
expect($lane->statusReads)->toBe([])
->and($lane->relayWrites)->toBe([
[selfserve_lane_relay::MACHINE, false],
[selfserve_lane_relay::MACHINE_CLEANER, false],
]);
});