diff --git a/src/gateway/http-common.test.ts b/src/gateway/http-common.test.ts index d55e3de072d..fb0c17628df 100644 --- a/src/gateway/http-common.test.ts +++ b/src/gateway/http-common.test.ts @@ -34,6 +34,22 @@ beforeEach(() => { resetDiagnosticEventsForTest(); }); +function headerNames(setHeader: ReturnType): string[] { + return setHeader.mock.calls + .map((call) => call[0]) + .filter((name): name is string => typeof name === "string"); +} + +function expectHeaderNotSet(setHeader: ReturnType, name: string): void { + expect(headerNames(setHeader)).not.toContain(name); +} + +function mockCallRecord(mock: ReturnType, index: number): unknown[] { + const call = mock.mock.calls[index]; + expect(call).toBeTruthy(); + return call ?? []; +} + describe("setDefaultSecurityHeaders", () => { it("sets X-Content-Type-Options", () => { const { res, setHeader } = makeMockHttpResponse(); @@ -70,19 +86,19 @@ describe("setDefaultSecurityHeaders", () => { it("does not set Strict-Transport-Security when not provided", () => { const { res, setHeader } = makeMockHttpResponse(); setDefaultSecurityHeaders(res); - expect(setHeader).not.toHaveBeenCalledWith("Strict-Transport-Security", expect.anything()); + expectHeaderNotSet(setHeader, "Strict-Transport-Security"); }); it("does not set Strict-Transport-Security for empty string", () => { const { res, setHeader } = makeMockHttpResponse(); setDefaultSecurityHeaders(res, { strictTransportSecurity: "" }); - expect(setHeader).not.toHaveBeenCalledWith("Strict-Transport-Security", expect.anything()); + expectHeaderNotSet(setHeader, "Strict-Transport-Security"); }); it("does not set Strict-Transport-Security when opts is omitted", () => { const { res, setHeader } = makeMockHttpResponse(); setDefaultSecurityHeaders(res, undefined); - expect(setHeader).not.toHaveBeenCalledWith("Strict-Transport-Security", expect.anything()); + expectHeaderNotSet(setHeader, "Strict-Transport-Security"); }); }); @@ -138,7 +154,7 @@ describe("sendRateLimited", () => { const { res, setHeader, end } = makeMockHttpResponse(); sendRateLimited(res); expect(res.statusCode).toBe(429); - expect(setHeader).not.toHaveBeenCalledWith("Retry-After", expect.anything()); + expectHeaderNotSet(setHeader, "Retry-After"); expect(end).toHaveBeenCalledWith( JSON.stringify({ error: { @@ -153,14 +169,14 @@ describe("sendRateLimited", () => { const { res, setHeader } = makeMockHttpResponse(); sendRateLimited(res, 0); expect(res.statusCode).toBe(429); - expect(setHeader).not.toHaveBeenCalledWith("Retry-After", expect.anything()); + expectHeaderNotSet(setHeader, "Retry-After"); }); it("responds with 429 and no Retry-After when retryAfterMs is negative", () => { const { res, setHeader } = makeMockHttpResponse(); sendRateLimited(res, -500); expect(res.statusCode).toBe(429); - expect(setHeader).not.toHaveBeenCalledWith("Retry-After", expect.anything()); + expectHeaderNotSet(setHeader, "Retry-After"); }); it("sets Retry-After (seconds, ceiled) when retryAfterMs is positive", () => { @@ -209,9 +225,10 @@ describe("readJsonBodyOrError", () => { it("returns the parsed body on success", async () => { readJsonBodyMock.mockResolvedValueOnce({ ok: true, value: { hello: "world" } }); const { res } = makeMockHttpResponse(); - const result = await readJsonBodyOrError(makeRequest(), res, 1024); + const req = makeRequest(); + const result = await readJsonBodyOrError(req, res, 1024); expect(result).toEqual({ hello: "world" }); - expect(readJsonBodyMock).toHaveBeenCalledWith(expect.anything(), 1024); + expect(readJsonBodyMock).toHaveBeenCalledWith(req, 1024); }); it("responds with 413 when the body is too large", async () => { @@ -229,16 +246,12 @@ describe("readJsonBodyOrError", () => { error: { message: "Payload too large", type: "invalid_request_error" }, }), ); - expect(events).toContainEqual( - expect.objectContaining({ - type: "payload.large", - surface: "gateway.http.json", - action: "rejected", - bytes: 2048, - limitBytes: 1024, - reason: "json_body_limit", - }), - ); + const event = events.find((entry) => entry.type === "payload.large"); + expect(event?.surface).toBe("gateway.http.json"); + expect(event?.action).toBe("rejected"); + expect(event?.bytes).toBe(2048); + expect(event?.limitBytes).toBe(1024); + expect(event?.reason).toBe("json_body_limit"); }); it("responds with 408 when the request body times out", async () => { @@ -366,8 +379,12 @@ describe("watchClientDisconnect", () => { const { req, res } = buildReqRes(reqSocket, resSocket); const controller = new AbortController(); watchClientDisconnect(req, res, controller); - expect(reqOn).toHaveBeenCalledWith("close", expect.any(Function)); - expect(resOn).toHaveBeenCalledWith("close", expect.any(Function)); + const reqOnCall = mockCallRecord(reqOn, 0); + const resOnCall = mockCallRecord(resOn, 0); + expect(reqOnCall[0]).toBe("close"); + expect(typeof reqOnCall[1]).toBe("function"); + expect(resOnCall[0]).toBe("close"); + expect(typeof resOnCall[1]).toBe("function"); }); it("cleanup detaches the close listener from each socket", () => {