|
1 | 1 | // Gateway Network Client tests cover gateway network client script behavior. |
2 | 2 | import { EventEmitter } from "node:events"; |
3 | | -import { describe, expect, it } from "vitest"; |
| 3 | +import { describe, expect, it, vi } from "vitest"; |
4 | 4 | import { runGatewayNetworkClient } from "../../scripts/e2e/lib/gateway-network/client.mjs"; |
5 | 5 | import { readGatewayNetworkClientConnectTimeoutMs } from "../../scripts/e2e/lib/gateway-network/limits.mjs"; |
6 | 6 | import { onceFrame } from "../../scripts/e2e/lib/gateway-network/ws-frames.mjs"; |
@@ -126,7 +126,11 @@ describe("gateway network WebSocket open guard", () => { |
126 | 126 | stdout, |
127 | 127 | deps: { |
128 | 128 | delay: async () => {}, |
129 | | - onceFrame: async (_ws: unknown, predicate: (frame: unknown) => boolean) => { |
| 129 | + onceFrame: async ( |
| 130 | + _ws: unknown, |
| 131 | + predicate: (frame: unknown) => boolean, |
| 132 | + _timeoutMs?: number, |
| 133 | + ) => { |
130 | 134 | const frame = { |
131 | 135 | type: "res", |
132 | 136 | id: sentMethods.at(-1) === "connect" ? "c1" : "h1", |
@@ -157,6 +161,59 @@ describe("gateway network WebSocket open guard", () => { |
157 | 161 | expect(harness.closeCount).toBe(1); |
158 | 162 | }); |
159 | 163 |
|
| 164 | + it("bounds socket and frame waits by the client deadline", async () => { |
| 165 | + const harness = createNetworkClientHarness([{ ok: true }, healthResponse()]); |
| 166 | + const openSocket = vi.fn(harness.deps.openSocket); |
| 167 | + const onceFrame = vi.fn(harness.deps.onceFrame); |
| 168 | + |
| 169 | + await runGatewayNetworkClient( |
| 170 | + { token: "secret-token", url: "ws://127.0.0.1:12345", timeoutMs: 250 }, |
| 171 | + { |
| 172 | + ...harness.deps, |
| 173 | + onceFrame, |
| 174 | + openSocket, |
| 175 | + }, |
| 176 | + ); |
| 177 | + |
| 178 | + expect(openSocket.mock.calls[0]?.[1]).toBeGreaterThan(0); |
| 179 | + expect(openSocket.mock.calls[0]?.[1]).toBeLessThanOrEqual(250); |
| 180 | + expect(onceFrame.mock.calls.map((call) => call[2])).toHaveLength(2); |
| 181 | + for (const frameTimeoutMs of onceFrame.mock.calls.map((call) => call[2])) { |
| 182 | + expect(frameTimeoutMs).toBeGreaterThan(0); |
| 183 | + expect(frameTimeoutMs).toBeLessThanOrEqual(250); |
| 184 | + } |
| 185 | + }); |
| 186 | + |
| 187 | + it("does not sleep past the remaining client deadline between retries", async () => { |
| 188 | + const delays: number[] = []; |
| 189 | + let now = 1_000; |
| 190 | + |
| 191 | + const dateSpy = vi.spyOn(Date, "now").mockImplementation(() => now); |
| 192 | + try { |
| 193 | + await expect( |
| 194 | + runGatewayNetworkClient( |
| 195 | + { token: "secret-token", url: "ws://127.0.0.1:12345", timeoutMs: 250 }, |
| 196 | + { |
| 197 | + delay: async (ms: number) => { |
| 198 | + delays.push(ms); |
| 199 | + now += ms; |
| 200 | + }, |
| 201 | + openSocket: async () => { |
| 202 | + now += 200; |
| 203 | + throw new Error("ECONNREFUSED"); |
| 204 | + }, |
| 205 | + protocolVersion: 1, |
| 206 | + stdout: () => {}, |
| 207 | + }, |
| 208 | + ), |
| 209 | + ).rejects.toThrow("ECONNREFUSED"); |
| 210 | + } finally { |
| 211 | + dateSpy.mockRestore(); |
| 212 | + } |
| 213 | + |
| 214 | + expect(delays).toEqual([50]); |
| 215 | + }); |
| 216 | + |
160 | 217 | it("fails a connected socket whose health success lacks summary evidence", async () => { |
161 | 218 | const harness = createNetworkClientHarness([{ ok: true }, { ok: true }]); |
162 | 219 |
|
|
0 commit comments