|
1 | 1 | import { EventEmitter } from "node:events"; |
2 | | -import { beforeEach, describe, expect, it, vi } from "vitest"; |
| 2 | +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
3 | 3 | import type { WebSocketServer } from "ws"; |
4 | 4 | import type { ResolvedGatewayAuth } from "../auth.js"; |
5 | 5 |
|
@@ -36,6 +36,10 @@ describe("attachGatewayWsConnectionHandler", () => { |
36 | 36 | attachGatewayWsMessageHandlerMock.mockReset(); |
37 | 37 | }); |
38 | 38 |
|
| 39 | + afterEach(() => { |
| 40 | + vi.useRealTimers(); |
| 41 | + }); |
| 42 | + |
39 | 43 | it("threads current auth getters into the handshake handler instead of a stale snapshot", () => { |
40 | 44 | const listeners = new Map<string, (...args: unknown[]) => void>(); |
41 | 45 | const wss = { |
@@ -132,6 +136,7 @@ describe("attachGatewayWsConnectionHandler", () => { |
132 | 136 | port: 19001, |
133 | 137 | canvasHostEnabled: false, |
134 | 138 | resolvedAuth: createResolvedAuth("token"), |
| 139 | + preauthHandshakeTimeoutMs: 60_000, |
135 | 140 | gatewayMethods: [], |
136 | 141 | events: [], |
137 | 142 | refreshHealthSnapshot: vi.fn(), |
@@ -167,4 +172,76 @@ describe("attachGatewayWsConnectionHandler", () => { |
167 | 172 | expect(registered).toBe(false); |
168 | 173 | expect(clients.size).toBe(0); |
169 | 174 | }); |
| 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 | + }); |
170 | 247 | }); |
0 commit comments