From a7181a4ab289d678df074f05ebce5fe4b5d6a09a Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Wed, 8 Jul 2026 18:46:33 +0200 Subject: [PATCH] Fix edge gateway relay binding reactivation Reactivate existing relay binding rows when a gateway/relay pair is re-added after soft deletion, avoiding duplicate uniq_edge_gateway_binding inserts. Add regression coverage for the reactivation path. --- .../classes/edge_gateway_manager.php | 2 +- .../EdgeGatewayBackendIntegrationTest.php | 16 ++++++++++++++++ .../EdgeGatewayManagerCommandQueueTest.php | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/services/nginx/app/modules/edgegateway/classes/edge_gateway_manager.php b/services/nginx/app/modules/edgegateway/classes/edge_gateway_manager.php index 97f994fb..ac4acaa3 100644 --- a/services/nginx/app/modules/edgegateway/classes/edge_gateway_manager.php +++ b/services/nginx/app/modules/edgegateway/classes/edge_gateway_manager.php @@ -799,7 +799,6 @@ class edge_gateway_manager $existing = (new edge_gateway_relay_bindings_o())->getFieldsWhere([ 'gateway_id' => $gatewayId, 'relay_id' => $relayId, - 'deleted_at' => null, ], ['id']); if ($existing !== []) { @@ -818,6 +817,7 @@ class edge_gateway_manager $bindingObject->approved_by->set($userId); $bindingObject->approved_at->set($this->now()); $bindingObject->metadata_json->set($bindingMetadata); + $bindingObject->deleted_at->set(null); continue; } diff --git a/services/nginx/app/tests/Integration/EdgeGateway/EdgeGatewayBackendIntegrationTest.php b/services/nginx/app/tests/Integration/EdgeGateway/EdgeGatewayBackendIntegrationTest.php index 03672d7c..61fb4f1e 100644 --- a/services/nginx/app/tests/Integration/EdgeGateway/EdgeGatewayBackendIntegrationTest.php +++ b/services/nginx/app/tests/Integration/EdgeGateway/EdgeGatewayBackendIntegrationTest.php @@ -358,6 +358,22 @@ it('persists gateway cutover relay bindings used by self-serve Shelly dispatch', ->toHaveKey('fallback_mode', edge_gateway_manager::RELAY_FALLBACK_PREFER_LOCAL) ->and($context['manager']->getDepartmentTransportMode((int)$department['id'])) ->toBe(edge_gateway_manager::TRANSPORT_MODE_GATEWAY); + + $context['manager']->setRelayBindings($gatewayId, [], (int)$user['id']); + $recreatedBindings = $context['manager']->setRelayBindings($gatewayId, [[ + 'relay_id' => 'relay-machine', + 'device_id' => 'device-machine-recreated', + 'local_ip' => '10.50.60.71', + 'channel' => 1, + ]], (int)$user['id']); + + expect($recreatedBindings)->toHaveCount(1) + ->and($recreatedBindings[0])->toMatchArray([ + 'relay_id' => 'relay-machine', + 'device_id' => 'device-machine-recreated', + 'local_ip' => '10.50.60.71', + 'channel' => 1, + ]); } finally { $context['cleanup']->run(); } diff --git a/services/nginx/app/tests/Unit/Selfserve/EdgeGatewayManagerCommandQueueTest.php b/services/nginx/app/tests/Unit/Selfserve/EdgeGatewayManagerCommandQueueTest.php index 6a1b8433..e926fa72 100644 --- a/services/nginx/app/tests/Unit/Selfserve/EdgeGatewayManagerCommandQueueTest.php +++ b/services/nginx/app/tests/Unit/Selfserve/EdgeGatewayManagerCommandQueueTest.php @@ -104,6 +104,25 @@ it('resolves relay bindings without requiring a primary department gateway first ->and($body)->toContain('heartbeatTimestamp'); }); +it('reactivates soft-deleted relay bindings before inserting replacements', function (): void { + $managerSource = file_get_contents(app_path('classes/edge_gateway_manager.php')); + + expect($managerSource)->not->toBeFalse(); + preg_match( + '/public function setRelayBindings\(int \$gatewayId, array \$bindings, \?int \$userId = null\): array\s*\{(?P.*?)\n \}\n\n \/\*\*/s', + (string)$managerSource, + $matches + ); + + expect($matches)->toHaveKey('body'); + $body = (string)$matches['body']; + + expect($body)->toContain("'gateway_id' => \$gatewayId") + ->and($body)->toContain("'relay_id' => \$relayId") + ->and($body)->toContain('$bindingObject->deleted_at->set(null);') + ->and($body)->not->toContain("'deleted_at' => null,\n ], ['id']);"); +}); + it('loads relay command helpers on the manager and gateway operations on the dedicated service', function (): void { $reflection = new ReflectionClass(edge_gateway_manager::class); $operationServiceReflection = new ReflectionClass(\classes\edge_gateway_operation_service::class);