























@@ -1,5 +1,5 @@
11import { EventEmitter } from "node:events";
2-import { beforeEach, describe, expect, it, vi } from "vitest";
2+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33import type { WebSocketServer } from "ws";
44import type { ResolvedGatewayAuth } from "../auth.js";
55@@ -36,6 +36,10 @@ describe("attachGatewayWsConnectionHandler", () => {
3636attachGatewayWsMessageHandlerMock.mockReset();
3737});
383839+afterEach(() => {
40+vi.useRealTimers();
41+});
42+3943it("threads current auth getters into the handshake handler instead of a stale snapshot", () => {
4044const listeners = new Map<string, (...args: unknown[]) => void>();
4145const wss = {
@@ -132,6 +136,7 @@ describe("attachGatewayWsConnectionHandler", () => {
132136port: 19001,
133137canvasHostEnabled: false,
134138resolvedAuth: createResolvedAuth("token"),
139+preauthHandshakeTimeoutMs: 60_000,
135140gatewayMethods: [],
136141events: [],
137142refreshHealthSnapshot: vi.fn(),
@@ -167,4 +172,76 @@ describe("attachGatewayWsConnectionHandler", () => {
167172expect(registered).toBe(false);
168173expect(clients.size).toBe(0);
169174});
175+176+it("sends protocol pings until the connection closes", () => {
177+vi.useFakeTimers();
178+const listeners = new Map<string, (...args: unknown[]) => void>();
179+const wss = {
180+on: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
181+listeners.set(event, handler);
182+}),
183+} as unknown as WebSocketServer;
184+const socket = Object.assign(new EventEmitter(), {
185+_socket: {
186+remoteAddress: "127.0.0.1",
187+remotePort: 1234,
188+localAddress: "127.0.0.1",
189+localPort: 5678,
190+},
191+send: vi.fn(),
192+ping: vi.fn(),
193+close: vi.fn(),
194+});
195+const upgradeReq = {
196+headers: { host: "127.0.0.1:19001" },
197+socket: { localAddress: "127.0.0.1" },
198+};
199+200+attachGatewayWsConnectionHandler({
201+ wss,
202+clients: new Set(),
203+preauthConnectionBudget: { release: vi.fn() } as never,
204+port: 19001,
205+canvasHostEnabled: false,
206+resolvedAuth: createResolvedAuth("token"),
207+preauthHandshakeTimeoutMs: 60_000,
208+gatewayMethods: [],
209+events: [],
210+refreshHealthSnapshot: vi.fn(),
211+logGateway: createLogger() as never,
212+logHealth: createLogger() as never,
213+logWsControl: createLogger() as never,
214+extraHandlers: {},
215+broadcast: vi.fn(),
216+buildRequestContext: () =>
217+({
218+unsubscribeAllSessionEvents: vi.fn(),
219+nodeRegistry: { unregister: vi.fn() },
220+nodeUnsubscribeAll: vi.fn(),
221+}) as never,
222+});
223+224+const onConnection = listeners.get("connection");
225+expect(onConnection).toBeTypeOf("function");
226+onConnection?.(socket, upgradeReq);
227+228+const passed = attachGatewayWsMessageHandlerMock.mock.calls[0]?.[0] as {
229+setClient: (client: unknown) => boolean;
230+};
231+expect(
232+passed.setClient({
233+ socket,
234+connect: { client: { id: "openclaw-control-ui", mode: "webchat" } },
235+connId: "ping-client",
236+usesSharedGatewayAuth: false,
237+}),
238+).toBe(true);
239+240+vi.advanceTimersByTime(25_000);
241+expect(socket.ping).toHaveBeenCalledTimes(1);
242+243+socket.emit("close", 1000, Buffer.from("done"));
244+vi.advanceTimersByTime(25_000);
245+expect(socket.ping).toHaveBeenCalledTimes(1);
246+});
170247});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。