Add new tests for shell bridge and broker to handle structured failures and invalid session handling
- Add tests for shell bridge to validate structured error reporting on spawn failures. - Add broker tests to ensure proper rejection of malformed browser shell upgrades without leaking sensitive tokens. - Update `.env.example` with `EDGE_PUBLIC_BROKER_URL` for public access configuration.
This commit is contained in:
@@ -49,6 +49,7 @@ ECONOMIC_API_APP_SECRET_TOKEN=
|
||||
|
||||
# Edge broker defaults for shell relay and gateway dispatch.
|
||||
EDGE_BROKER_URL=http://edge-broker:4300
|
||||
EDGE_PUBLIC_BROKER_URL=http://localhost/api/edge-broker
|
||||
EDGE_BROKER_SHARED_SECRET=truckwash-edge-dev
|
||||
|
||||
# Redis credentials
|
||||
|
||||
@@ -70,6 +70,11 @@ When updating `.env` values used by PHP containers (for example e-conomic tokens
|
||||
docker compose up -d --force-recreate php1 php2 php3 php4 php5 php-cron
|
||||
```
|
||||
|
||||
#### Edge Broker Public URL
|
||||
Set `EDGE_PUBLIC_BROKER_URL` to the public route that serves the edge broker, including the path prefix handled by the proxy. Local Traefik uses `http://localhost/api/edge-broker`; production routes use the public broker prefix, for example `https://api.truckwash.dk/edge-broker`.
|
||||
|
||||
The browser terminal connects to the exact advertised `EDGE_PUBLIC_BROKER_URL` plus `/ws/browser-shell`. That URL must be routable through the proxy to the edge-broker service. Do not rely on derived `/api/edge-broker` fallback paths outside the local Traefik setup.
|
||||
|
||||
## Testing
|
||||
|
||||
The project now uses [Pest](https://pestphp.com/) as the primary test runner in `services/nginx/app`.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -625,6 +625,39 @@ test("shell bridge proxies PTY output, input, resize, close, and dispose events"
|
||||
assert.ok(messages.some((message) => message.type === "SHELL_EXIT" && message.code === 0));
|
||||
});
|
||||
|
||||
test("shell bridge reports structured spawn failures", async () => {
|
||||
const messages = [];
|
||||
const shell = createShellBridge(
|
||||
(message) => messages.push(message),
|
||||
{
|
||||
createPtyProcess: async () => {
|
||||
throw new Error("node-pty unavailable");
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await shell.open({ sessionId: "spawn-failure-shell" });
|
||||
|
||||
assert.ok(
|
||||
messages.some(
|
||||
(message) =>
|
||||
message.type === "SHELL_OUTPUT" &&
|
||||
message.sessionId === "spawn-failure-shell" &&
|
||||
/Failed to start root shell: node-pty unavailable/.test(message.data)
|
||||
)
|
||||
);
|
||||
assert.ok(
|
||||
messages.some(
|
||||
(message) =>
|
||||
message.type === "SHELL_EXIT" &&
|
||||
message.sessionId === "spawn-failure-shell" &&
|
||||
message.code === 1 &&
|
||||
message.reason === "shell_spawn_failed" &&
|
||||
message.message === "node-pty unavailable"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("startAgent reports API polling metadata, executes polled commands, and uploads shell events", async () => {
|
||||
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-"));
|
||||
const configPath = path.join(tempDir, "config.json");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import net from "node:net";
|
||||
import WebSocket from "ws";
|
||||
|
||||
import { createBrokerServer } from "../server.mjs";
|
||||
@@ -18,6 +19,32 @@ function waitForClose(socket) {
|
||||
});
|
||||
}
|
||||
|
||||
function rawUpgradeRequest(port, path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.createConnection({ host: "127.0.0.1", port }, () => {
|
||||
socket.write(
|
||||
[
|
||||
`GET ${path} HTTP/1.1`,
|
||||
`Host: 127.0.0.1:${port}`,
|
||||
"Connection: Upgrade",
|
||||
"Upgrade: websocket",
|
||||
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==",
|
||||
"Sec-WebSocket-Version: 13",
|
||||
"",
|
||||
"",
|
||||
].join("\r\n")
|
||||
);
|
||||
});
|
||||
let response = "";
|
||||
socket.setEncoding("utf8");
|
||||
socket.on("data", (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
socket.on("end", () => resolve(response));
|
||||
socket.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitFor(predicate, { timeoutMs = 1000, intervalMs = 10, description = "condition" } = {}) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
|
||||
@@ -192,6 +219,83 @@ test("broker closes browser shell sessions immediately when no agent is connecte
|
||||
await broker.close();
|
||||
});
|
||||
|
||||
test("broker rejects browser shell upgrades without a token using HTTP diagnostics", async () => {
|
||||
const broker = createBrokerServer({ authMode: "stub" });
|
||||
const address = await broker.listen(0);
|
||||
const port = address.port;
|
||||
|
||||
const response = await rawUpgradeRequest(port, "/ws/browser-shell");
|
||||
|
||||
assert.match(response, /^HTTP\/1\.1 400 Bad Request/m);
|
||||
assert.match(response, /"error_code":"shell_session_token_missing"/);
|
||||
assert.match(response, /"message":"Missing shell session token\."/);
|
||||
|
||||
await broker.close();
|
||||
});
|
||||
|
||||
test("broker rejects invalid browser shell upgrades without leaking the token", async () => {
|
||||
const broker = createBrokerServer({
|
||||
authMode: "stub",
|
||||
validateShellSession: async () => {
|
||||
const error = new Error("Shell session expired");
|
||||
error.status = 401;
|
||||
error.code = "shell_session_expired";
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
const address = await broker.listen(0);
|
||||
const port = address.port;
|
||||
const rawToken = "session-token-secret";
|
||||
|
||||
const response = await rawUpgradeRequest(port, `/ws/browser-shell?token=${rawToken}`);
|
||||
|
||||
assert.match(response, /^HTTP\/1\.1 401 Unauthorized/m);
|
||||
assert.match(response, /"error_code":"shell_session_expired"/);
|
||||
assert.doesNotMatch(response, new RegExp(rawToken));
|
||||
|
||||
await broker.close();
|
||||
});
|
||||
|
||||
test("broker closes browser shell sessions when the agent never reports shell opened", async () => {
|
||||
const closedSessions = [];
|
||||
const broker = createBrokerServer({
|
||||
authMode: "stub",
|
||||
shellOpenTimeoutMs: 30,
|
||||
validateShellSession: async () => ({ id: "shell-timeout", gateway_id: "701", reason: "diagnostic" }),
|
||||
closeShellSession: async (_id, _token, transcript, reason, details) => {
|
||||
closedSessions.push({ transcript, reason, details });
|
||||
},
|
||||
});
|
||||
const address = await broker.listen(0);
|
||||
const port = address.port;
|
||||
|
||||
const agent = new WebSocket(`ws://127.0.0.1:${port}/ws/agent?gatewayId=701&token=agent-token`);
|
||||
const agentMessages = collectMessages(agent);
|
||||
await new Promise((resolve) => agent.once("open", resolve));
|
||||
|
||||
const browser = new WebSocket(`ws://127.0.0.1:${port}/ws/browser-shell?token=session-token`);
|
||||
const browserMessages = collectMessages(browser);
|
||||
await new Promise((resolve) => browser.once("open", resolve));
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
agentMessages.some(
|
||||
(message) => message.type === "OPEN_ROOT_SHELL" && message.payload.sessionId === "shell-timeout"
|
||||
),
|
||||
{ description: "agent shell open request before timeout" }
|
||||
);
|
||||
await waitForClose(browser);
|
||||
await waitFor(() => closedSessions.length === 1, { description: "timeout shell session close callback" });
|
||||
|
||||
assert.ok(browserMessages.some((message) => message.type === "closed" && message.reason === "shell_open_timeout"));
|
||||
assert.equal(closedSessions[0].reason, "shell_open_timeout");
|
||||
assert.equal(closedSessions[0].details.stage, "shell_open");
|
||||
assert.equal(closedSessions[0].details.code, 1011);
|
||||
|
||||
agent.terminate();
|
||||
await broker.close();
|
||||
});
|
||||
|
||||
test("broker closes browser shell sessions when the agent disconnects before shell open", async () => {
|
||||
const closedSessions = [];
|
||||
const broker = createBrokerServer({
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -11868,3 +11868,255 @@
|
||||
[Thu Apr 23 19:30:02 2026] 127.0.0.1:38996 Closing
|
||||
[Thu Apr 23 19:30:10 2026] 127.0.0.1:59878 Accepted
|
||||
[Thu Apr 23 19:30:16 2026] 127.0.0.1:59878 Closing
|
||||
[Mon Apr 27 15:43:00 2026] PHP 8.2.15 Development Server (http://127.0.0.1:39017) started
|
||||
[Mon Apr 27 15:43:00 2026] 127.0.0.1:43538 Accepted
|
||||
[Mon Apr 27 15:43:02 2026] 127.0.0.1:43538 Closing
|
||||
[Mon Apr 27 15:43:02 2026] 127.0.0.1:59780 Accepted
|
||||
[Mon Apr 27 15:43:09 2026] 127.0.0.1:59780 Closing
|
||||
[Mon Apr 27 15:43:09 2026] 127.0.0.1:59790 Accepted
|
||||
[Mon Apr 27 15:43:21 2026] 127.0.0.1:59790 Closing
|
||||
[Mon Apr 27 15:43:21 2026] 127.0.0.1:55448 Accepted
|
||||
[Mon Apr 27 15:43:37 2026] 127.0.0.1:55448 Closing
|
||||
[Mon Apr 27 15:43:37 2026] 127.0.0.1:39854 Accepted
|
||||
[Mon Apr 27 15:43:49 2026] 127.0.0.1:39854 Closing
|
||||
[Mon Apr 27 15:43:49 2026] 127.0.0.1:40450 Accepted
|
||||
[Mon Apr 27 15:44:00 2026] 127.0.0.1:40450 Closing
|
||||
[Mon Apr 27 15:44:00 2026] 127.0.0.1:37108 Accepted
|
||||
[Mon Apr 27 15:44:04 2026] 127.0.0.1:37108 Closing
|
||||
[Mon Apr 27 15:44:04 2026] 127.0.0.1:51378 Accepted
|
||||
[Mon Apr 27 15:44:20 2026] 127.0.0.1:51378 Closing
|
||||
[Mon Apr 27 15:44:20 2026] 127.0.0.1:57924 Accepted
|
||||
[Mon Apr 27 15:44:26 2026] 127.0.0.1:57924 Closing
|
||||
[Mon Apr 27 15:44:26 2026] 127.0.0.1:36924 Accepted
|
||||
[Mon Apr 27 15:44:33 2026] 127.0.0.1:36924 Closing
|
||||
[Mon Apr 27 15:44:33 2026] 127.0.0.1:41488 Accepted
|
||||
[Mon Apr 27 15:44:50 2026] 127.0.0.1:41488 Closing
|
||||
[Mon Apr 27 15:44:50 2026] 127.0.0.1:42138 Accepted
|
||||
[Mon Apr 27 15:45:06 2026] 127.0.0.1:42138 Closing
|
||||
[Mon Apr 27 15:45:06 2026] 127.0.0.1:56450 Accepted
|
||||
[Mon Apr 27 15:45:24 2026] 127.0.0.1:56450 Closing
|
||||
[Mon Apr 27 15:45:31 2026] 127.0.0.1:50392 Accepted
|
||||
[Mon Apr 27 15:45:45 2026] 127.0.0.1:50392 Closing
|
||||
[Mon Apr 27 15:45:51 2026] 127.0.0.1:44926 Accepted
|
||||
[Mon Apr 27 15:46:21 2026] 127.0.0.1:44926 Closing
|
||||
[Mon Apr 27 15:46:21 2026] 127.0.0.1:34068 Accepted
|
||||
[Mon Apr 27 15:46:29 2026] PHP 8.2.15 Development Server (http://127.0.0.1:39779) started
|
||||
[Mon Apr 27 15:46:29 2026] 127.0.0.1:36812 Accepted
|
||||
[Mon Apr 27 15:46:30 2026] 127.0.0.1:34068 Closing
|
||||
[Mon Apr 27 15:46:30 2026] 127.0.0.1:45262 Accepted
|
||||
[Mon Apr 27 15:46:31 2026] 127.0.0.1:36812 Closing
|
||||
[Mon Apr 27 15:46:31 2026] 127.0.0.1:53156 Accepted
|
||||
[Mon Apr 27 15:46:39 2026] 127.0.0.1:45262 Closing
|
||||
[Mon Apr 27 15:46:39 2026] 127.0.0.1:45266 Accepted
|
||||
[Mon Apr 27 15:46:44 2026] 127.0.0.1:53156 Closing
|
||||
[Mon Apr 27 15:46:52 2026] 127.0.0.1:45266 Closing
|
||||
[Mon Apr 27 15:46:52 2026] 127.0.0.1:37766 Accepted
|
||||
[Mon Apr 27 15:47:10 2026] PHP 8.2.15 Development Server (http://127.0.0.1:39511) started
|
||||
[Mon Apr 27 15:47:11 2026] 127.0.0.1:41892 Accepted
|
||||
[Mon Apr 27 15:47:14 2026] 127.0.0.1:41892 Closing
|
||||
[Mon Apr 27 15:47:14 2026] 127.0.0.1:41904 Accepted
|
||||
[Mon Apr 27 15:47:17 2026] 127.0.0.1:37766 Closing
|
||||
[Mon Apr 27 15:47:17 2026] 127.0.0.1:36058 Accepted
|
||||
[Mon Apr 27 15:47:19 2026] 127.0.0.1:41904 Closing
|
||||
[Mon Apr 27 15:47:19 2026] 127.0.0.1:36924 Accepted
|
||||
[Mon Apr 27 15:47:26 2026] 127.0.0.1:36058 Closing
|
||||
[Mon Apr 27 15:47:26 2026] 127.0.0.1:51896 Accepted
|
||||
[Mon Apr 27 15:47:31 2026] 127.0.0.1:36924 Closing
|
||||
[Mon Apr 27 15:47:31 2026] 127.0.0.1:34276 Accepted
|
||||
[Mon Apr 27 15:47:46 2026] 127.0.0.1:34276 Closing
|
||||
[Mon Apr 27 15:47:46 2026] 127.0.0.1:37246 Accepted
|
||||
[Mon Apr 27 15:47:57 2026] 127.0.0.1:51896 Closing
|
||||
[Mon Apr 27 15:47:59 2026] 127.0.0.1:37246 Closing
|
||||
[Mon Apr 27 15:47:59 2026] 127.0.0.1:36472 Accepted
|
||||
[Mon Apr 27 15:48:03 2026] 127.0.0.1:33350 Accepted
|
||||
[Mon Apr 27 15:48:07 2026] 127.0.0.1:36472 Closing
|
||||
[Mon Apr 27 15:48:07 2026] 127.0.0.1:56178 Accepted
|
||||
[Mon Apr 27 15:48:07 2026] 127.0.0.1:33350 Closing
|
||||
[Mon Apr 27 15:48:10 2026] 127.0.0.1:40096 Accepted
|
||||
[Mon Apr 27 15:48:13 2026] 127.0.0.1:56178 Closing
|
||||
[Mon Apr 27 15:48:13 2026] 127.0.0.1:55944 Accepted
|
||||
[Mon Apr 27 15:48:19 2026] 127.0.0.1:40096 Closing
|
||||
[Mon Apr 27 15:48:19 2026] 127.0.0.1:40104 Accepted
|
||||
[Mon Apr 27 15:48:28 2026] 127.0.0.1:40104 Closing
|
||||
[Mon Apr 27 15:48:28 2026] 127.0.0.1:55654 Accepted
|
||||
[Mon Apr 27 15:48:30 2026] 127.0.0.1:55944 Closing
|
||||
[Mon Apr 27 15:48:30 2026] 127.0.0.1:41444 Accepted
|
||||
[Mon Apr 27 15:48:33 2026] 127.0.0.1:55654 Closing
|
||||
[Mon Apr 27 15:48:33 2026] 127.0.0.1:54932 Accepted
|
||||
[Mon Apr 27 15:48:36 2026] 127.0.0.1:41444 Closing
|
||||
[Mon Apr 27 15:48:36 2026] 127.0.0.1:41450 Accepted
|
||||
[Mon Apr 27 15:48:39 2026] 127.0.0.1:54932 Closing
|
||||
[Mon Apr 27 15:48:39 2026] 127.0.0.1:54942 Accepted
|
||||
[Mon Apr 27 15:48:46 2026] 127.0.0.1:41450 Closing
|
||||
[Mon Apr 27 15:48:46 2026] 127.0.0.1:42874 Accepted
|
||||
[Mon Apr 27 15:48:46 2026] 127.0.0.1:54942 Closing
|
||||
[Mon Apr 27 15:48:46 2026] 127.0.0.1:49554 Accepted
|
||||
[Mon Apr 27 15:49:03 2026] 127.0.0.1:49554 Closing
|
||||
[Mon Apr 27 15:49:03 2026] 127.0.0.1:51798 Accepted
|
||||
[Mon Apr 27 15:49:04 2026] 127.0.0.1:42874 Closing
|
||||
[Mon Apr 27 15:49:04 2026] 127.0.0.1:56176 Accepted
|
||||
[Mon Apr 27 15:49:11 2026] 127.0.0.1:51798 Closing
|
||||
[Mon Apr 27 15:49:11 2026] 127.0.0.1:47548 Accepted
|
||||
[Mon Apr 27 15:49:22 2026] 127.0.0.1:56176 Closing
|
||||
[Mon Apr 27 15:49:22 2026] 127.0.0.1:52542 Accepted
|
||||
[Mon Apr 27 15:49:24 2026] 127.0.0.1:47548 Closing
|
||||
[Mon Apr 27 15:49:24 2026] 127.0.0.1:39848 Accepted
|
||||
[Mon Apr 27 15:49:36 2026] 127.0.0.1:39848 Closing
|
||||
[Mon Apr 27 15:49:43 2026] 127.0.0.1:60636 Accepted
|
||||
[Mon Apr 27 15:49:43 2026] 127.0.0.1:52542 Closing
|
||||
[Mon Apr 27 15:49:50 2026] 127.0.0.1:43864 Accepted
|
||||
[Mon Apr 27 15:50:03 2026] 127.0.0.1:43864 Closing
|
||||
[Mon Apr 27 15:50:07 2026] 127.0.0.1:60636 Closing
|
||||
[Mon Apr 27 15:50:07 2026] 127.0.0.1:48092 Accepted
|
||||
[Mon Apr 27 15:50:11 2026] 127.0.0.1:38378 Accepted
|
||||
[Mon Apr 27 15:50:26 2026] 127.0.0.1:48092 Closing
|
||||
[Mon Apr 27 15:50:26 2026] 127.0.0.1:51390 Accepted
|
||||
[Mon Apr 27 15:50:41 2026] 127.0.0.1:38378 Closing
|
||||
[Mon Apr 27 15:50:49 2026] 127.0.0.1:38158 Accepted
|
||||
[Mon Apr 27 15:50:54 2026] 127.0.0.1:51390 Closing
|
||||
[Mon Apr 27 15:50:54 2026] 127.0.0.1:33842 Accepted
|
||||
[Mon Apr 27 15:50:56 2026] 127.0.0.1:38158 Closing
|
||||
[Mon Apr 27 15:51:00 2026] 127.0.0.1:36832 Accepted
|
||||
[Mon Apr 27 15:51:13 2026] 127.0.0.1:36832 Closing
|
||||
[Mon Apr 27 15:51:13 2026] 127.0.0.1:39172 Accepted
|
||||
[Mon Apr 27 15:51:19 2026] 127.0.0.1:33842 Closing
|
||||
[Mon Apr 27 15:51:19 2026] 127.0.0.1:47144 Accepted
|
||||
[Mon Apr 27 15:51:25 2026] 127.0.0.1:39172 Closing
|
||||
[Mon Apr 27 15:51:25 2026] 127.0.0.1:33908 Accepted
|
||||
[Mon Apr 27 15:51:32 2026] 127.0.0.1:47144 Closing
|
||||
[Mon Apr 27 15:51:32 2026] 127.0.0.1:35586 Accepted
|
||||
[Mon Apr 27 15:51:39 2026] 127.0.0.1:33908 Closing
|
||||
[Mon Apr 27 15:51:39 2026] 127.0.0.1:49634 Accepted
|
||||
[Mon Apr 27 15:51:49 2026] 127.0.0.1:49634 Closing
|
||||
[Mon Apr 27 15:51:49 2026] 127.0.0.1:35846 Accepted
|
||||
[Mon Apr 27 15:52:02 2026] 127.0.0.1:35846 Closing
|
||||
[Mon Apr 27 15:52:02 2026] 127.0.0.1:40002 Accepted
|
||||
[Mon Apr 27 15:52:03 2026] 127.0.0.1:35586 Closing
|
||||
[Mon Apr 27 15:52:09 2026] 127.0.0.1:38974 Accepted
|
||||
[Mon Apr 27 15:52:22 2026] 127.0.0.1:40002 Closing
|
||||
[Mon Apr 27 15:52:22 2026] 127.0.0.1:39160 Accepted
|
||||
[Mon Apr 27 15:52:22 2026] 127.0.0.1:38974 Closing
|
||||
[Mon Apr 27 15:52:23 2026] 127.0.0.1:50508 Accepted
|
||||
[Mon Apr 27 15:52:31 2026] 127.0.0.1:50508 Closing
|
||||
[Mon Apr 27 15:52:31 2026] 127.0.0.1:34868 Accepted
|
||||
[Mon Apr 27 15:52:33 2026] 127.0.0.1:39160 Closing
|
||||
[Mon Apr 27 15:52:33 2026] 127.0.0.1:51124 Accepted
|
||||
[Mon Apr 27 15:52:45 2026] 127.0.0.1:34868 Closing
|
||||
[Mon Apr 27 15:52:51 2026] 127.0.0.1:51124 Closing
|
||||
[Mon Apr 27 15:52:51 2026] 127.0.0.1:38142 Accepted
|
||||
[Mon Apr 27 15:52:58 2026] 127.0.0.1:40286 Accepted
|
||||
[Mon Apr 27 15:53:08 2026] 127.0.0.1:38142 Closing
|
||||
[Mon Apr 27 15:53:15 2026] 127.0.0.1:47064 Accepted
|
||||
[Mon Apr 27 15:53:43 2026] 127.0.0.1:47064 Closing
|
||||
[Mon Apr 27 15:53:43 2026] 127.0.0.1:40368 Accepted
|
||||
[Mon Apr 27 15:54:10 2026] 127.0.0.1:40286 Closing
|
||||
[Mon Apr 27 15:54:12 2026] 127.0.0.1:40368 Closing
|
||||
[Mon Apr 27 15:54:12 2026] 127.0.0.1:59302 Accepted
|
||||
[Mon Apr 27 15:54:44 2026] 127.0.0.1:59302 Closing
|
||||
[Mon Apr 27 15:54:51 2026] 127.0.0.1:55188 Accepted
|
||||
[Mon Apr 27 15:55:05 2026] 127.0.0.1:55188 Closing
|
||||
[Mon Apr 27 15:55:06 2026] 127.0.0.1:54998 Accepted
|
||||
[Mon Apr 27 15:55:14 2026] 127.0.0.1:54998 Closing
|
||||
[Mon Apr 27 15:55:14 2026] 127.0.0.1:55388 Accepted
|
||||
[Mon Apr 27 15:55:27 2026] 127.0.0.1:55388 Closing
|
||||
[Mon Apr 27 15:55:38 2026] 127.0.0.1:58096 Accepted
|
||||
[Mon Apr 27 15:56:40 2026] 127.0.0.1:58096 Closing
|
||||
[Mon Apr 27 15:57:41 2026] PHP 8.2.15 Development Server (http://127.0.0.1:43345) started
|
||||
[Mon Apr 27 15:57:41 2026] PHP 8.2.15 Development Server (http://127.0.0.1:45337) started
|
||||
[Mon Apr 27 15:57:41 2026] 127.0.0.1:54434 Accepted
|
||||
[Mon Apr 27 15:57:41 2026] 127.0.0.1:42766 Accepted
|
||||
[Mon Apr 27 15:57:46 2026] 127.0.0.1:42766 Closing
|
||||
[Mon Apr 27 15:57:46 2026] 127.0.0.1:54434 Closing
|
||||
[Mon Apr 27 15:57:46 2026] 127.0.0.1:42782 Accepted
|
||||
[Mon Apr 27 15:57:46 2026] 127.0.0.1:54438 Accepted
|
||||
[Mon Apr 27 15:57:54 2026] 127.0.0.1:54438 Closing
|
||||
[Mon Apr 27 15:57:54 2026] 127.0.0.1:43210 Accepted
|
||||
[Mon Apr 27 15:58:08 2026] 127.0.0.1:43210 Closing
|
||||
[Mon Apr 27 15:58:08 2026] 127.0.0.1:37286 Accepted
|
||||
[Mon Apr 27 15:58:10 2026] 127.0.0.1:42782 Closing
|
||||
[Mon Apr 27 15:58:10 2026] 127.0.0.1:57164 Accepted
|
||||
[Mon Apr 27 15:58:25 2026] 127.0.0.1:37286 Closing
|
||||
[Mon Apr 27 15:58:25 2026] 127.0.0.1:33692 Accepted
|
||||
[Mon Apr 27 15:58:31 2026] 127.0.0.1:57164 Closing
|
||||
[Mon Apr 27 15:58:31 2026] 127.0.0.1:41728 Accepted
|
||||
[Mon Apr 27 15:58:41 2026] 127.0.0.1:33692 Closing
|
||||
[Mon Apr 27 15:58:41 2026] 127.0.0.1:44756 Accepted
|
||||
[Mon Apr 27 15:58:52 2026] 127.0.0.1:44756 Closing
|
||||
[Mon Apr 27 15:58:52 2026] 127.0.0.1:41988 Accepted
|
||||
[Mon Apr 27 15:58:59 2026] 127.0.0.1:41988 Closing
|
||||
[Mon Apr 27 15:58:59 2026] 127.0.0.1:34238 Accepted
|
||||
[Mon Apr 27 15:59:01 2026] 127.0.0.1:41728 Closing
|
||||
[Mon Apr 27 15:59:01 2026] 127.0.0.1:41632 Accepted
|
||||
[Mon Apr 27 15:59:10 2026] 127.0.0.1:41632 Closing
|
||||
[Mon Apr 27 15:59:10 2026] 127.0.0.1:56798 Accepted
|
||||
[Mon Apr 27 15:59:17 2026] 127.0.0.1:34238 Closing
|
||||
[Mon Apr 27 15:59:17 2026] 127.0.0.1:51968 Accepted
|
||||
[Mon Apr 27 15:59:24 2026] 127.0.0.1:51968 Closing
|
||||
[Mon Apr 27 15:59:24 2026] 127.0.0.1:38554 Accepted
|
||||
[Mon Apr 27 15:59:28 2026] 127.0.0.1:56798 Closing
|
||||
[Mon Apr 27 15:59:28 2026] 127.0.0.1:37742 Accepted
|
||||
[Mon Apr 27 15:59:33 2026] 127.0.0.1:38554 Closing
|
||||
[Mon Apr 27 15:59:33 2026] 127.0.0.1:60924 Accepted
|
||||
[Mon Apr 27 15:59:52 2026] 127.0.0.1:60924 Closing
|
||||
[Mon Apr 27 15:59:52 2026] 127.0.0.1:43252 Accepted
|
||||
[Mon Apr 27 15:59:55 2026] 127.0.0.1:37742 Closing
|
||||
[Mon Apr 27 15:59:55 2026] 127.0.0.1:43598 Accepted
|
||||
[Mon Apr 27 16:00:08 2026] 127.0.0.1:43598 Closing
|
||||
[Mon Apr 27 16:00:08 2026] 127.0.0.1:45400 Accepted
|
||||
[Mon Apr 27 16:00:15 2026] 127.0.0.1:43252 Closing
|
||||
[Mon Apr 27 16:00:15 2026] 127.0.0.1:33680 Accepted
|
||||
[Mon Apr 27 16:00:28 2026] 127.0.0.1:45400 Closing
|
||||
[Mon Apr 27 16:00:28 2026] 127.0.0.1:58188 Accepted
|
||||
[Mon Apr 27 16:00:38 2026] 127.0.0.1:33680 Closing
|
||||
[Mon Apr 27 16:00:43 2026] 127.0.0.1:58188 Closing
|
||||
[Mon Apr 27 16:00:43 2026] 127.0.0.1:52096 Accepted
|
||||
[Mon Apr 27 16:01:17 2026] 127.0.0.1:52096 Closing
|
||||
[Mon Apr 27 16:02:10 2026] PHP 8.2.15 Development Server (http://127.0.0.1:37323) started
|
||||
[Mon Apr 27 16:02:10 2026] 127.0.0.1:35510 Accepted
|
||||
[Mon Apr 27 16:02:14 2026] 127.0.0.1:35510 Closing
|
||||
[Mon Apr 27 16:02:14 2026] 127.0.0.1:35512 Accepted
|
||||
[Mon Apr 27 16:02:34 2026] 127.0.0.1:35512 Closing
|
||||
[Mon Apr 27 16:02:35 2026] 127.0.0.1:50544 Accepted
|
||||
[Mon Apr 27 16:02:54 2026] 127.0.0.1:50544 Closing
|
||||
[Mon Apr 27 16:02:54 2026] 127.0.0.1:34284 Accepted
|
||||
[Mon Apr 27 16:03:23 2026] 127.0.0.1:34284 Closing
|
||||
[Mon Apr 27 16:03:23 2026] 127.0.0.1:58504 Accepted
|
||||
[Mon Apr 27 16:03:35 2026] 127.0.0.1:58504 Closing
|
||||
[Mon Apr 27 16:03:35 2026] 127.0.0.1:57200 Accepted
|
||||
[Mon Apr 27 16:03:55 2026] 127.0.0.1:57200 Closing
|
||||
[Mon Apr 27 16:03:56 2026] 127.0.0.1:37528 Accepted
|
||||
[Mon Apr 27 16:04:24 2026] 127.0.0.1:37528 Closing
|
||||
[Mon Apr 27 16:04:24 2026] 127.0.0.1:41972 Accepted
|
||||
[Mon Apr 27 16:04:48 2026] 127.0.0.1:41972 Closing
|
||||
[Mon Apr 27 16:04:48 2026] 127.0.0.1:40764 Accepted
|
||||
[Mon Apr 27 16:05:10 2026] 127.0.0.1:40764 Closing
|
||||
[Mon Apr 27 16:05:10 2026] 127.0.0.1:51052 Accepted
|
||||
[Mon Apr 27 16:05:25 2026] 127.0.0.1:51052 Closing
|
||||
[Mon Apr 27 16:05:25 2026] 127.0.0.1:49526 Accepted
|
||||
[Mon Apr 27 16:05:58 2026] 127.0.0.1:49526 Closing
|
||||
[Mon Apr 27 16:05:58 2026] 127.0.0.1:54380 Accepted
|
||||
[Mon Apr 27 16:06:10 2026] 127.0.0.1:54380 Closing
|
||||
[Mon Apr 27 16:06:10 2026] 127.0.0.1:38446 Accepted
|
||||
[Mon Apr 27 16:06:33 2026] 127.0.0.1:38446 Closing
|
||||
[Mon Apr 27 16:07:01 2026] PHP 8.2.15 Development Server (http://127.0.0.1:46263) started
|
||||
[Mon Apr 27 16:07:01 2026] PHP 8.2.15 Development Server (http://127.0.0.1:44107) started
|
||||
[Mon Apr 27 16:07:01 2026] 127.0.0.1:57266 Accepted
|
||||
[Mon Apr 27 16:07:01 2026] 127.0.0.1:47356 Accepted
|
||||
[Mon Apr 27 16:07:05 2026] 127.0.0.1:57266 Closing
|
||||
[Mon Apr 27 16:07:05 2026] 127.0.0.1:57282 Accepted
|
||||
[Mon Apr 27 16:07:05 2026] 127.0.0.1:47356 Closing
|
||||
[Mon Apr 27 16:07:05 2026] 127.0.0.1:47362 Accepted
|
||||
[Mon Apr 27 16:07:21 2026] 127.0.0.1:47362 Closing
|
||||
[Mon Apr 27 16:07:37 2026] 127.0.0.1:57282 Closing
|
||||
[Mon Apr 27 16:07:38 2026] 127.0.0.1:41372 Accepted
|
||||
[Mon Apr 27 16:07:53 2026] 127.0.0.1:41372 Closing
|
||||
[Mon Apr 27 16:07:53 2026] 127.0.0.1:39536 Accepted
|
||||
[Mon Apr 27 16:08:09 2026] 127.0.0.1:39536 Closing
|
||||
[Mon Apr 27 16:08:09 2026] 127.0.0.1:33514 Accepted
|
||||
[Mon Apr 27 16:08:28 2026] 127.0.0.1:33514 Closing
|
||||
[Mon Apr 27 16:08:28 2026] 127.0.0.1:59450 Accepted
|
||||
[Mon Apr 27 16:08:58 2026] 127.0.0.1:59450 Closing
|
||||
[Mon Apr 27 16:08:58 2026] 127.0.0.1:40070 Accepted
|
||||
[Mon Apr 27 16:09:11 2026] 127.0.0.1:40070 Closing
|
||||
[Mon Apr 27 16:09:12 2026] 127.0.0.1:41964 Accepted
|
||||
[Mon Apr 27 16:09:54 2026] 127.0.0.1:41964 Closing
|
||||
|
||||
@@ -181,6 +181,7 @@ it('validates broker sessions and ingests presence, telemetry, logs, and shell l
|
||||
'message' => 'Shell exited cleanly.',
|
||||
'code' => 0,
|
||||
'stage' => 'shell_active',
|
||||
'ws_url' => 'wss://broker.example.test/ws/browser-shell?token=raw-shell-token',
|
||||
],
|
||||
edge_test_broker_headers()
|
||||
);
|
||||
@@ -201,7 +202,9 @@ it('validates broker sessions and ingests presence, telemetry, logs, and shell l
|
||||
->and($closedShell->data()['metadata']['close_code'] ?? null)
|
||||
->toBe(0)
|
||||
->and($closedShell->data()['metadata']['close_stage'] ?? null)
|
||||
->toBe('shell_active');
|
||||
->toBe('shell_active')
|
||||
->and($closedShell->data()['metadata']['close_diagnostics']['ws_url'] ?? null)
|
||||
->toBe('wss://broker.example.test/ws/browser-shell?token=***');
|
||||
|
||||
$logsPage = api_client()->get('/edge-gateways/' . (int)$gateway['id'] . '/logs', $session['headers']);
|
||||
|
||||
|
||||
@@ -270,6 +270,20 @@ it('manages edge gateway metadata, bindings, operations, sessions, rotation, cut
|
||||
->toHaveKey('token')
|
||||
->toHaveKey('ws_url');
|
||||
|
||||
$brokerPresence = api_client()->post(
|
||||
'/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/presence',
|
||||
[
|
||||
'status' => 'connected',
|
||||
'connection_id' => 'operator-shell-broker-1',
|
||||
],
|
||||
edge_test_broker_headers()
|
||||
);
|
||||
|
||||
$brokerPresence
|
||||
->assertStatus(200)
|
||||
->assertEnvelope()
|
||||
->assertSuccess();
|
||||
|
||||
$shellSession = api_client()->post(
|
||||
'/edge-gateways/' . (int)$gateway['id'] . '/shell-sessions',
|
||||
[
|
||||
@@ -290,7 +304,9 @@ it('manages edge gateway metadata, bindings, operations, sessions, rotation, cut
|
||||
->toHaveKey('token')
|
||||
->toHaveKey('session')
|
||||
->and($shellSession->data()['session']['reason'] ?? null)
|
||||
->toBe('Operator smoke session');
|
||||
->toBe('Operator smoke session')
|
||||
->and($shellSession->data()['diagnostics']['broker_presence']['connection_id'] ?? null)
|
||||
->toBe('operator-shell-broker-1');
|
||||
|
||||
$rotateResponse = api_client()->post(
|
||||
'/edge-gateways/' . (int)$gateway['id'] . '/rotate-credentials',
|
||||
|
||||
+21
-10
@@ -169,6 +169,14 @@ it('assembles tasks, logs, statistics, operations, commands, and shell lifecycle
|
||||
->and($commandResult['job']['status'] ?? null)
|
||||
->toBe('COMPLETED');
|
||||
|
||||
$context['manager']->recordBrokerPresence(
|
||||
$gatewayId,
|
||||
'connected',
|
||||
'broker-connection-1',
|
||||
null,
|
||||
['transport' => 'ws']
|
||||
);
|
||||
|
||||
$shellSession = $context['manager']->createShellSession(
|
||||
$gatewayId,
|
||||
(int)$user['id'],
|
||||
@@ -179,6 +187,8 @@ it('assembles tasks, logs, statistics, operations, commands, and shell lifecycle
|
||||
);
|
||||
$shellToken = (string)($shellSession['token'] ?? '');
|
||||
expect($shellToken)->not->toBe('');
|
||||
expect($shellSession['diagnostics']['broker_presence']['connection_id'] ?? null)
|
||||
->toBe('broker-connection-1');
|
||||
|
||||
$validatedShell = $context['manager']->validateShellSessionToken($shellToken);
|
||||
expect($validatedShell)->toHaveKey('status', 'PENDING');
|
||||
@@ -189,12 +199,21 @@ it('assembles tasks, logs, statistics, operations, commands, and shell lifecycle
|
||||
$closedShell = $context['manager']->closeShellSessionByToken(
|
||||
$shellToken,
|
||||
"edge-shell-output\n",
|
||||
'agent_exit'
|
||||
'agent_exit',
|
||||
[
|
||||
'message' => 'Integration shell completed.',
|
||||
'code' => 0,
|
||||
'stage' => 'shell_active',
|
||||
]
|
||||
);
|
||||
expect($closedShell)
|
||||
->toHaveKey('status', 'COMPLETED')
|
||||
->and($closedShell['transcript'] ?? null)
|
||||
->toBe("edge-shell-output\n");
|
||||
->toBe("edge-shell-output\n")
|
||||
->and($closedShell['metadata']['close_reason'] ?? null)
|
||||
->toBe('agent_exit')
|
||||
->and($closedShell['metadata']['close_stage'] ?? null)
|
||||
->toBe('shell_active');
|
||||
|
||||
$context['manager']->appendGatewayLogEntry(
|
||||
$gatewayId,
|
||||
@@ -205,14 +224,6 @@ it('assembles tasks, logs, statistics, operations, commands, and shell lifecycle
|
||||
['source' => 'integration']
|
||||
);
|
||||
|
||||
$context['manager']->recordBrokerPresence(
|
||||
$gatewayId,
|
||||
'connected',
|
||||
'broker-connection-1',
|
||||
null,
|
||||
['transport' => 'ws']
|
||||
);
|
||||
|
||||
$context['manager']->recordTelemetryFromBroker($gatewayId, [
|
||||
'status' => 'ONLINE',
|
||||
'metadata' => [
|
||||
|
||||
Reference in New Issue
Block a user