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:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user