From 3b3ed31bb76c5709b0058a85e98ef2ca6b2256bc Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Mon, 1 Jun 2026 23:45:20 +0200 Subject: [PATCH] Cap self-serve gate relay timers --- services/edge-agent/dist/agent.mjs | 3 ++- services/edge-agent/test/agent.test.mjs | 25 +++++++++++++++++++ .../selfserve_lane_port_controller_t.php | 3 ++- .../nginx/app/routes/moduleSelfServeRoute.php | 2 ++ .../SelfserveLanePortControllerTest.php | 18 +++++++++++++ .../Selfserve/SelfserveRouteWiringTest.php | 2 ++ 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/services/edge-agent/dist/agent.mjs b/services/edge-agent/dist/agent.mjs index c85c2bcd..86ecf401 100644 --- a/services/edge-agent/dist/agent.mjs +++ b/services/edge-agent/dist/agent.mjs @@ -18,6 +18,7 @@ const DEFAULT_UPDATE_VERIFY_INTERVAL_MS = 500; const DEFAULT_UPDATE_RESTART_GRACE_MS = 150; const DEFAULT_BROKER_RECONNECT_DELAY_MS = 1500; const DEFAULT_SHELLY_LOCAL_HTTP_TIMEOUT_MS = 1200; +const MAX_RELAY_TOGGLE_AFTER_SECONDS = 5; const UPDATE_VERIFY_COMMAND = "post-update-verify"; const execFile = promisify(execFileCallback); @@ -482,7 +483,7 @@ function resolveRelayToggleAfterSeconds(payload = {}) { return null; } - return Math.floor(configured); + return Math.min(Math.floor(configured), MAX_RELAY_TOGGLE_AFTER_SECONDS); } async function fetchJson(url, fetchImpl = fetch, options = {}) { diff --git a/services/edge-agent/test/agent.test.mjs b/services/edge-agent/test/agent.test.mjs index d79ec584..96665a88 100644 --- a/services/edge-agent/test/agent.test.mjs +++ b/services/edge-agent/test/agent.test.mjs @@ -271,6 +271,31 @@ test("relay switch commands pass timer values to local Shelly APIs", async () => "http://10.1.0.31/rpc/Switch.Set?id=0&on=true&toggle_after=3", "http://10.1.0.31/relay/0?turn=on&timer=3", ]); + + const cappedUrls = []; + const cappedFetch = async (url) => { + cappedUrls.push(String(url)); + + return { + ok: true, + async json() { + return { output: true }; + }, + }; + }; + + await setRelayState({ + localIp: "10.1.0.31", + channel: 0, + on: true, + toggle_after: 999999999, + device_generation: 3, + }, cappedFetch); + + assert.equal( + cappedUrls[0], + "http://10.1.0.31/rpc/Switch.Set?id=0&on=true&toggle_after=5" + ); }); test("runUpdate stages a pending verification restart after installing new artifacts", async () => { diff --git a/services/nginx/app/modules/selfserve/traits/selfserve_lane_port_controller_t.php b/services/nginx/app/modules/selfserve/traits/selfserve_lane_port_controller_t.php index 96b2ebe1..a622dbcb 100644 --- a/services/nginx/app/modules/selfserve/traits/selfserve_lane_port_controller_t.php +++ b/services/nginx/app/modules/selfserve/traits/selfserve_lane_port_controller_t.php @@ -17,6 +17,7 @@ trait selfserve_lane_port_controller_t { private const DEMO_RELAY_ID_PREFIX = 'demo-'; private const DEFAULT_PORT_OPEN_TOGGLE_AFTER_SECONDS = 1; + private const MAX_PORT_OPEN_TOGGLE_AFTER_SECONDS = 5; /** * Open the lane port @@ -105,7 +106,7 @@ trait selfserve_lane_port_controller_t private function normalizePortOpenToggleAfter(?int $toggle_after_seconds): int { if ($toggle_after_seconds !== null && $toggle_after_seconds > 0) { - return $toggle_after_seconds; + return min($toggle_after_seconds, self::MAX_PORT_OPEN_TOGGLE_AFTER_SECONDS); } return self::DEFAULT_PORT_OPEN_TOGGLE_AFTER_SECONDS; diff --git a/services/nginx/app/routes/moduleSelfServeRoute.php b/services/nginx/app/routes/moduleSelfServeRoute.php index d69c70ae..4d25a500 100644 --- a/services/nginx/app/routes/moduleSelfServeRoute.php +++ b/services/nginx/app/routes/moduleSelfServeRoute.php @@ -30,6 +30,7 @@ class moduleSelfServeRoute { use route_t; + private const MAX_GATE_OPEN_TOGGLE_AFTER_SECONDS = 5; private const CUSTOMER_SELFSERVE_PERMISSION = 'list_own_department_selfserve_vehicle_conditions'; public function run(): void @@ -1596,6 +1597,7 @@ class moduleSelfServeRoute } self::requireMinValue($toggle_after, 1); + self::requireMaxValue($toggle_after, self::MAX_GATE_OPEN_TOGGLE_AFTER_SECONDS); return $toggle_after; } diff --git a/services/nginx/app/tests/Unit/Selfserve/SelfserveLanePortControllerTest.php b/services/nginx/app/tests/Unit/Selfserve/SelfserveLanePortControllerTest.php index d4f6cc9d..e1b58caa 100644 --- a/services/nginx/app/tests/Unit/Selfserve/SelfserveLanePortControllerTest.php +++ b/services/nginx/app/tests/Unit/Selfserve/SelfserveLanePortControllerTest.php @@ -152,6 +152,24 @@ it('passes explicit timer values when opening lane gates', function (): void { ]); }); +it('caps excessive timer values when opening lane gates', function (): void { + $lane = new SelfserveLanePortControllerHarness( + relayInId: 'relay-in-123', + relayOutId: 'relay-out-123' + ); + + $result = $lane->open(selfserve_lane_port::ENTRANCE, 999999999); + + expect($result)->toBeTrue(); + expect($lane->switchFake->switchCalls)->toBe([ + [ + 'id' => 'relay-in-123', + 'on' => true, + 'toggle_after' => 5, + ], + ]); +}); + it('keeps demo relay gate-open queued but skips Shelly switch calls', function (): void { $lane = new SelfserveLanePortControllerHarness( relayInId: 'demo-relay-in', diff --git a/services/nginx/app/tests/Unit/Selfserve/SelfserveRouteWiringTest.php b/services/nginx/app/tests/Unit/Selfserve/SelfserveRouteWiringTest.php index 102416a4..b6f7a12a 100644 --- a/services/nginx/app/tests/Unit/Selfserve/SelfserveRouteWiringTest.php +++ b/services/nginx/app/tests/Unit/Selfserve/SelfserveRouteWiringTest.php @@ -233,6 +233,8 @@ it('wires self-serve lane gate open endpoint', function (): void { expect($moduleSelfServeRoute)->toContain('selfserve_lane_port::ENTRANCE'); expect($moduleSelfServeRoute)->toContain('selfserve_lane_port::EXIT'); expect($moduleSelfServeRoute)->toContain('$toggle_after = $this->requestedRelayToggleAfter(1);'); + expect($moduleSelfServeRoute)->toContain('MAX_GATE_OPEN_TOGGLE_AFTER_SECONDS = 5'); + expect($moduleSelfServeRoute)->toContain('self::requireMaxValue($toggle_after, self::MAX_GATE_OPEN_TOGGLE_AFTER_SECONDS);'); expect($moduleSelfServeRoute)->toContain('$lane->open($gate, $toggle_after)'); expect($moduleSelfServeRoute)->toContain("'toggle_after' => \$toggle_after"); expect($moduleSelfServeRoute)->toContain('private function requestedRelayToggleAfter');