


























@@ -1,131 +1,100 @@
1-import http from "node:http";
21import { afterEach, describe, expect, it, vi } from "vitest";
3-import { connectGatewayClient, disconnectGatewayClient } from "./test-helpers.e2e.js";
2+import { WebSocket } from "ws";
3+import { createEmptyPluginRegistry } from "../plugins/registry.js";
4+import { createGatewayRuntimeState } from "./server-runtime-state.js";
45import { getFreePort, installGatewayTestHooks, startGatewayServer } from "./test-helpers.js";
566-const machineNameDelay = vi.hoisted(() => {
7-let enteredResolve = () => {};
8-let releaseResolve = () => {};
9-let entered = new Promise<void>((resolve) => {
10-enteredResolve = resolve;
11-});
12-let release = new Promise<void>((resolve) => {
13-releaseResolve = resolve;
14-});
15-return {
16-waitUntilDelayed: async () => {
17-await entered;
18-},
19-release: () => {
20-releaseResolve();
21-},
22-reset: () => {
23-entered = new Promise<void>((resolve) => {
24-enteredResolve = resolve;
25-});
26-release = new Promise<void>((resolve) => {
27-releaseResolve = resolve;
28-});
29-},
30-run: async () => {
31-enteredResolve();
32-await release;
33-return "test-machine";
34-},
35-};
36-});
37-38-vi.mock("../infra/machine-name.js", () => ({
39-getMachineDisplayName: () => machineNameDelay.run(),
40-}));
7+type StartGatewayServer = typeof import("./test-helpers.js").startGatewayServer;
8+type GatewayServerForTest = Awaited<ReturnType<StartGatewayServer>>;
4194210installGatewayTestHooks({ scope: "suite" });
431112+async function connectWebSocket(url: string): Promise<WebSocket> {
13+const ws = new WebSocket(url);
14+return await new Promise<WebSocket>((resolve, reject) => {
15+const timeout = setTimeout(() => {
16+cleanup();
17+ws.close();
18+reject(new Error("expected websocket connect to succeed immediately after startup"));
19+}, 5_000);
20+timeout.unref?.();
21+const cleanup = () => {
22+clearTimeout(timeout);
23+ws.off("open", handleOpen);
24+ws.off("error", handleError);
25+};
26+const handleOpen = () => {
27+cleanup();
28+resolve(ws);
29+};
30+const handleError = (err: Error) => {
31+cleanup();
32+reject(err);
33+};
34+ws.once("open", handleOpen);
35+ws.once("error", handleError);
36+});
37+}
38+39+async function disconnectWebSocket(ws: WebSocket): Promise<void> {
40+if (ws.readyState === WebSocket.CLOSED) {
41+return;
42+}
43+await new Promise<void>((resolve) => {
44+ws.once("close", () => resolve());
45+ws.close();
46+});
47+}
48+4449afterEach(() => {
45-machineNameDelay.release();
50+vi.restoreAllMocks();
4651});
47524853describe("gateway startup websocket readiness", () => {
49-it("does not bind the websocket port until websocket handlers are attached", async () => {
50-machineNameDelay.reset();
51-const previousMinimal = process.env.OPENCLAW_TEST_MINIMAL_GATEWAY;
52-process.env.OPENCLAW_TEST_MINIMAL_GATEWAY = "0";
53-let server: Awaited<ReturnType<typeof startGatewayServer>> | undefined;
54+it("attaches websocket upgrade handlers before exposing the listen step", async () => {
55+const registry = createEmptyPluginRegistry();
56+const runtimeState = await createGatewayRuntimeState({
57+cfg: {},
58+bindHost: "127.0.0.1",
59+port: 0,
60+controlUiEnabled: false,
61+controlUiBasePath: "/",
62+openAiChatCompletionsEnabled: false,
63+openResponsesEnabled: false,
64+resolvedAuth: {} as never,
65+getResolvedAuth: () => ({}) as never,
66+hooksConfig: () => null,
67+getHookClientIpConfig: () => ({}) as never,
68+pluginRegistry: registry,
69+deps: {} as never,
70+log: { info: () => {}, warn: () => {} },
71+logHooks: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} } as never,
72+logPlugins: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} } as never,
73+});
5474try {
55-const port = await getFreePort();
56-const startup = startGatewayServer(port, {
57-auth: { mode: "none" },
58-});
59-await machineNameDelay.waitUntilDelayed();
60-61-const pendingUpgrade = await new Promise<
62-{ kind: "error"; code?: string } | { kind: "response"; status: number; body: string }
63->((resolve) => {
64-const req = http.request({
65-host: "127.0.0.1",
66- port,
67-path: "/",
68-headers: {
69-Connection: "Upgrade",
70-Upgrade: "websocket",
71-"Sec-WebSocket-Key": "dGVzdC1rZXktMDEyMzQ1Ng==",
72-"Sec-WebSocket-Version": "13",
73-},
74-});
75-req.once("error", (err) => {
76-resolve({ kind: "error", code: (err as NodeJS.ErrnoException).code });
77-});
78-req.once("response", (res) => {
79-let body = "";
80-res.setEncoding("utf8");
81-res.on("data", (chunk) => {
82-body += chunk;
83-});
84-res.once("end", () => {
85-resolve({ kind: "response", status: res.statusCode ?? 0, body });
86-});
87-});
88-req.end();
89-});
90-91-expect(pendingUpgrade).toEqual({ kind: "error", code: "ECONNREFUSED" });
92-93-machineNameDelay.release();
94-server = await startup;
75+expect(runtimeState.httpBindHosts).toEqual([]);
76+expect(runtimeState.httpServer.listenerCount("upgrade")).toBeGreaterThan(0);
9577} finally {
96-machineNameDelay.release();
97-if (server) {
98-await server.close();
99-}
100-if (previousMinimal === undefined) {
101-delete process.env.OPENCLAW_TEST_MINIMAL_GATEWAY;
102-} else {
103-process.env.OPENCLAW_TEST_MINIMAL_GATEWAY = previousMinimal;
104-}
78+runtimeState.releasePluginRouteRegistry();
79+runtimeState.wss.close();
10580}
10681});
1078210883it("accepts an immediate websocket connection once startup resolves", async () => {
109-machineNameDelay.reset();
11084const previousMinimal = process.env.OPENCLAW_TEST_MINIMAL_GATEWAY;
11185process.env.OPENCLAW_TEST_MINIMAL_GATEWAY = "0";
112-let server: Awaited<ReturnType<typeof startGatewayServer>> | undefined;
113-let client: Awaited<ReturnType<typeof connectGatewayClient>> | undefined;
86+let server: GatewayServerForTest | undefined;
87+let client: WebSocket | undefined;
11488try {
11589const port = await getFreePort();
116-machineNameDelay.release();
11790server = await startGatewayServer(port, {
11891auth: { mode: "none" },
11992});
12093121-client = await connectGatewayClient({
122-url: `ws://127.0.0.1:${port}`,
123-timeoutMs: 5_000,
124-timeoutMessage: "expected websocket connect to succeed immediately after startup",
125-});
94+client = await connectWebSocket(`ws://127.0.0.1:${port}`);
12695} finally {
12796if (client) {
128-await disconnectGatewayClient(client);
97+await disconnectWebSocket(client);
12998}
13099if (server) {
131100await server.close();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。