diff --git a/scripts/edge-gateway-e2e.mjs b/scripts/edge-gateway-e2e.mjs index 2de069c7..cae1fd82 100644 --- a/scripts/edge-gateway-e2e.mjs +++ b/scripts/edge-gateway-e2e.mjs @@ -581,6 +581,38 @@ function summarizeStreamMessages(messages, limit = 12) { .filter(Boolean); } +async function readGatewayDiagnostics({ baseUrl, authToken, gatewayId, containerName }) { + const diagnostics = {}; + + if (gatewayId !== null && gatewayId > 0 && authToken) { + try { + const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, { + token: authToken, + }); + diagnostics.gateway = { + status: detail?.data?.status ?? null, + channelStatus: detail?.data?.channel_status ?? null, + brokerPresence: detail?.data?.metadata?.broker_presence ?? null, + brokerConnected: detail?.data?.metadata?.broker_connected ?? null, + brokerLastError: detail?.data?.metadata?.broker_last_error ?? null, + }; + } catch (error) { + diagnostics.gatewayError = error instanceof Error ? error.message : String(error); + } + } + + try { + const logs = await runCommand("docker", ["logs", "--tail", "120", containerName], { + allowFailure: true, + }); + diagnostics.containerLogs = String(`${logs.stdout || ""}${logs.stderr || ""}`).trim().split(/\r?\n/).slice(-120); + } catch (error) { + diagnostics.containerLogError = error instanceof Error ? error.message : String(error); + } + + return diagnostics; +} + async function main() { const scriptPath = fileURLToPath(import.meta.url); const rootDir = await resolveRootDir(scriptPath); @@ -695,22 +727,35 @@ async function main() { } ); - await waitForCondition( - async () => { - const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, { - token: authToken, - }); + try { + await waitForCondition( + async () => { + const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, { + token: authToken, + }); - return Boolean( - detail?.data?.channel_status?.broker?.connected - || detail?.data?.metadata?.broker_connected - ); - }, - { - timeoutMs: 90_000, - message: "Gateway never established a live broker connection after install.", - } - ); + return Boolean( + detail?.data?.channel_status?.broker?.connected + || detail?.data?.metadata?.broker_connected + ); + }, + { + timeoutMs: 90_000, + message: "Gateway never established a live broker connection after install.", + } + ); + } catch (error) { + const diagnostics = await readGatewayDiagnostics({ + baseUrl, + authToken, + gatewayId, + containerName, + }); + throw new Error([ + error instanceof Error ? error.message : String(error), + `Broker diagnostics: ${JSON.stringify(diagnostics, null, 2)}`, + ].join("\n")); + } const WebSocketImpl = await loadWebSocketImplementation(); const streamSession = await apiRequest(baseUrl, "POST", `/edge-gateways/${gatewayId}/stream-session`, { diff --git a/services/nginx/app/resources/edge-gateway-agent/agent.php b/services/nginx/app/resources/edge-gateway-agent/agent.php index 62d14359..4c70fefb 100644 --- a/services/nginx/app/resources/edge-gateway-agent/agent.php +++ b/services/nginx/app/resources/edge-gateway-agent/agent.php @@ -727,15 +727,20 @@ final class BrokerWebSocketClient throw new RuntimeException('Invalid broker websocket URL: ' . $socketUrl); } - if ($host === 'edge-broker' && str_starts_with($path, '/edge-broker/')) { + $host = (string)$parts['host']; + $path = (string)($parts['path'] ?? '/'); + if ($host === 'edge-broker' && ($path === '/edge-broker' || str_starts_with($path, '/edge-broker/'))) { $path = substr($path, strlen('/edge-broker')); + if ($path === '') { + $path = '/'; + } } return [ 'scheme' => strtolower((string)($parts['scheme'] ?? 'ws')), - 'host' => (string)$parts['host'], + 'host' => $host, 'port' => (int)($parts['port'] ?? (((string)($parts['scheme'] ?? 'ws')) === 'wss' ? 443 : 80)), - 'path' => (string)($parts['path'] ?? '/') + 'path' => $path . (isset($parts['query']) && trim((string)$parts['query']) !== '' ? '?' . $parts['query'] : ''), ]; } diff --git a/services/nginx/app/tests/Api/EdgeGatewayExpectedRelayStatesApiTest.php b/services/nginx/app/tests/Api/EdgeGatewayExpectedRelayStatesApiTest.php index 8cba7bdd..03183304 100644 --- a/services/nginx/app/tests/Api/EdgeGatewayExpectedRelayStatesApiTest.php +++ b/services/nginx/app/tests/Api/EdgeGatewayExpectedRelayStatesApiTest.php @@ -51,6 +51,7 @@ it('serves whole-department expected relay states and records successful physica $queueResponse = api_client()->post('/modules/self-serve/lane/hardware/batch/set', [ 'lane_id' => $laneId, + 'transport' => 'gateway', 'commands' => [ ['target' => 'MACHINE', 'on' => true], ['target' => 'CLEANER', 'on' => false],