|
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 | |
|