Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
248
test/scripts/gateway-network-client.test.ts
Normal file
248
test/scripts/gateway-network-client.test.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
// Gateway Network Client tests cover gateway network client script behavior.
|
||||
import { EventEmitter } from "node:events";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { runGatewayNetworkClient } from "../../scripts/e2e/lib/gateway-network/client.mjs";
|
||||
import { readGatewayNetworkClientConnectTimeoutMs } from "../../scripts/e2e/lib/gateway-network/limits.mjs";
|
||||
import { onceFrame } from "../../scripts/e2e/lib/gateway-network/ws-frames.mjs";
|
||||
|
||||
describe("gateway network WebSocket open guard", () => {
|
||||
function healthResponse() {
|
||||
return {
|
||||
ok: true,
|
||||
payload: {
|
||||
agents: [],
|
||||
channelOrder: [],
|
||||
channels: {},
|
||||
defaultAgentId: "codex",
|
||||
durationMs: 3,
|
||||
ok: true,
|
||||
sessions: { count: 0, path: "/state/sessions", recent: [] },
|
||||
ts: Date.now(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
it("rejects loose client timeout env values instead of parsing prefixes", () => {
|
||||
expect(() =>
|
||||
readGatewayNetworkClientConnectTimeoutMs({
|
||||
OPENCLAW_GATEWAY_NETWORK_CLIENT_CONNECT_TIMEOUT_MS: "100ms",
|
||||
}),
|
||||
).toThrow("invalid OPENCLAW_GATEWAY_NETWORK_CLIENT_CONNECT_TIMEOUT_MS: 100ms");
|
||||
expect(() =>
|
||||
readGatewayNetworkClientConnectTimeoutMs({
|
||||
OPENCLAW_GATEWAY_NETWORK_CONNECT_READY_TIMEOUT_MS: "1e3",
|
||||
}),
|
||||
).toThrow("invalid OPENCLAW_GATEWAY_NETWORK_CONNECT_READY_TIMEOUT_MS: 1e3");
|
||||
expect(() =>
|
||||
readGatewayNetworkClientConnectTimeoutMs({
|
||||
OPENCLAW_GATEWAY_NETWORK_CLIENT_CONNECT_TIMEOUT_MS: "0",
|
||||
}),
|
||||
).toThrow("invalid OPENCLAW_GATEWAY_NETWORK_CLIENT_CONNECT_TIMEOUT_MS: 0");
|
||||
});
|
||||
|
||||
it("prefers the explicit client timeout over the connect-ready fallback", () => {
|
||||
expect(
|
||||
readGatewayNetworkClientConnectTimeoutMs({
|
||||
OPENCLAW_GATEWAY_NETWORK_CLIENT_CONNECT_TIMEOUT_MS: "5000",
|
||||
OPENCLAW_GATEWAY_NETWORK_CONNECT_READY_TIMEOUT_MS: "1000",
|
||||
}),
|
||||
).toBe(5000);
|
||||
expect(
|
||||
readGatewayNetworkClientConnectTimeoutMs({
|
||||
OPENCLAW_GATEWAY_NETWORK_CONNECT_READY_TIMEOUT_MS: "3000",
|
||||
}),
|
||||
).toBe(3000);
|
||||
});
|
||||
|
||||
it("resolves matching frames and ignores unrelated frames", async () => {
|
||||
const ws = new EventEmitter();
|
||||
const frame = onceFrame(ws, (message) => message?.id === "target", 1000);
|
||||
|
||||
ws.emit("message", JSON.stringify({ id: "noise" }));
|
||||
ws.emit("message", JSON.stringify({ id: "target", ok: true }));
|
||||
|
||||
await expect(frame).resolves.toEqual({ id: "target", ok: true });
|
||||
});
|
||||
|
||||
it("times out when no matching frame arrives", async () => {
|
||||
const ws = new EventEmitter();
|
||||
const frame = onceFrame(ws, () => false, 10);
|
||||
|
||||
ws.emit("message", JSON.stringify({ id: "noise" }));
|
||||
|
||||
await expect(frame).rejects.toThrow("timeout");
|
||||
});
|
||||
|
||||
it("rejects frame waits immediately when the socket closes", async () => {
|
||||
const ws = new EventEmitter();
|
||||
const startedAt = Date.now();
|
||||
const frame = onceFrame(ws, () => false, 1000);
|
||||
|
||||
ws.emit("close", 1006, Buffer.from("bye"));
|
||||
|
||||
await expect(frame).rejects.toThrow("closed before frame: 1006 bye");
|
||||
expect(Date.now() - startedAt).toBeLessThan(250);
|
||||
});
|
||||
|
||||
it("rejects frame waits immediately on socket errors", async () => {
|
||||
const ws = new EventEmitter();
|
||||
const frame = onceFrame(ws, () => false, 1000);
|
||||
|
||||
ws.emit("error", new Error("socket exploded"));
|
||||
|
||||
await expect(frame).rejects.toThrow("socket exploded");
|
||||
});
|
||||
|
||||
it("rejects invalid JSON frames instead of crashing the process", async () => {
|
||||
const ws = new EventEmitter();
|
||||
const frame = onceFrame(ws, () => false, 1000);
|
||||
|
||||
ws.emit("message", "{nope");
|
||||
|
||||
await expect(frame).rejects.toThrow();
|
||||
});
|
||||
|
||||
function createNetworkClientHarness(
|
||||
responses: Array<{ error?: { message?: string }; ok: boolean }>,
|
||||
) {
|
||||
const frames = [...responses];
|
||||
const sentMethods: string[] = [];
|
||||
const stdout: string[] = [];
|
||||
let closeCount = 0;
|
||||
const socket = {
|
||||
close: () => {
|
||||
closeCount += 1;
|
||||
},
|
||||
send: (payload: string) => {
|
||||
sentMethods.push(JSON.parse(payload).method);
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
get closeCount() {
|
||||
return closeCount;
|
||||
},
|
||||
sentMethods,
|
||||
stdout,
|
||||
deps: {
|
||||
delay: async () => {},
|
||||
onceFrame: async (
|
||||
_ws: unknown,
|
||||
predicate: (frame: unknown) => boolean,
|
||||
_timeoutMs?: number,
|
||||
) => {
|
||||
const frame = {
|
||||
type: "res",
|
||||
id: sentMethods.at(-1) === "connect" ? "c1" : "h1",
|
||||
...frames.shift(),
|
||||
};
|
||||
expect(predicate(frame)).toBe(true);
|
||||
return frame;
|
||||
},
|
||||
openSocket: async () => socket,
|
||||
protocolVersion: 1,
|
||||
stdout: (message: string) => {
|
||||
stdout.push(message);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
it("proves health after the authenticated connect handshake", async () => {
|
||||
const harness = createNetworkClientHarness([{ ok: true }, healthResponse()]);
|
||||
|
||||
await runGatewayNetworkClient(
|
||||
{ token: "secret-token", url: "ws://127.0.0.1:12345", timeoutMs: 1000 },
|
||||
harness.deps,
|
||||
);
|
||||
|
||||
expect(harness.sentMethods).toEqual(["connect", "health"]);
|
||||
expect(harness.stdout).toEqual(["ok"]);
|
||||
expect(harness.closeCount).toBe(1);
|
||||
});
|
||||
|
||||
it("bounds socket and frame waits by the client deadline", async () => {
|
||||
const harness = createNetworkClientHarness([{ ok: true }, healthResponse()]);
|
||||
const openSocket = vi.fn(harness.deps.openSocket);
|
||||
const onceFrame = vi.fn(harness.deps.onceFrame);
|
||||
|
||||
await runGatewayNetworkClient(
|
||||
{ token: "secret-token", url: "ws://127.0.0.1:12345", timeoutMs: 250 },
|
||||
{
|
||||
...harness.deps,
|
||||
onceFrame,
|
||||
openSocket,
|
||||
},
|
||||
);
|
||||
|
||||
expect(openSocket.mock.calls[0]?.[1]).toBeGreaterThan(0);
|
||||
expect(openSocket.mock.calls[0]?.[1]).toBeLessThanOrEqual(250);
|
||||
expect(onceFrame.mock.calls.map((call) => call[2])).toHaveLength(2);
|
||||
for (const frameTimeoutMs of onceFrame.mock.calls.map((call) => call[2])) {
|
||||
expect(frameTimeoutMs).toBeGreaterThan(0);
|
||||
expect(frameTimeoutMs).toBeLessThanOrEqual(250);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not sleep past the remaining client deadline between retries", async () => {
|
||||
const delays: number[] = [];
|
||||
let now = 1_000;
|
||||
|
||||
const dateSpy = vi.spyOn(Date, "now").mockImplementation(() => now);
|
||||
try {
|
||||
await expect(
|
||||
runGatewayNetworkClient(
|
||||
{ token: "secret-token", url: "ws://127.0.0.1:12345", timeoutMs: 250 },
|
||||
{
|
||||
delay: async (ms: number) => {
|
||||
delays.push(ms);
|
||||
now += ms;
|
||||
},
|
||||
openSocket: async () => {
|
||||
now += 200;
|
||||
throw new Error("ECONNREFUSED");
|
||||
},
|
||||
protocolVersion: 1,
|
||||
stdout: () => {},
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("ECONNREFUSED");
|
||||
} finally {
|
||||
dateSpy.mockRestore();
|
||||
}
|
||||
|
||||
expect(delays).toEqual([50]);
|
||||
});
|
||||
|
||||
it("fails a connected socket whose health success lacks summary evidence", async () => {
|
||||
const harness = createNetworkClientHarness([{ ok: true }, { ok: true }]);
|
||||
|
||||
await expect(
|
||||
runGatewayNetworkClient(
|
||||
{ token: "secret-token", url: "ws://127.0.0.1:12345", timeoutMs: 1000 },
|
||||
harness.deps,
|
||||
),
|
||||
).rejects.toThrow("health failed: missing health summary payload");
|
||||
|
||||
expect(harness.sentMethods).toEqual(["connect", "health"]);
|
||||
expect(harness.stdout).toEqual([]);
|
||||
expect(harness.closeCount).toBe(1);
|
||||
});
|
||||
|
||||
it("fails a connected socket whose health probe fails", async () => {
|
||||
const harness = createNetworkClientHarness([
|
||||
{ ok: true },
|
||||
{ ok: false, error: { message: "health unavailable" } },
|
||||
]);
|
||||
|
||||
await expect(
|
||||
runGatewayNetworkClient(
|
||||
{ token: "secret-token", url: "ws://127.0.0.1:12345", timeoutMs: 1000 },
|
||||
harness.deps,
|
||||
),
|
||||
).rejects.toThrow("health failed: health unavailable");
|
||||
|
||||
expect(harness.sentMethods).toEqual(["connect", "health"]);
|
||||
expect(harness.closeCount).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user