From b5633698e27eb3c7d318dc4b4a63539c7c4fb539 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 16:05:49 +0100 Subject: [PATCH] test: clear signal client container broad matchers --- .../signal/src/client-container.test.ts | 198 ++++++++---------- 1 file changed, 93 insertions(+), 105 deletions(-) diff --git a/extensions/signal/src/client-container.test.ts b/extensions/signal/src/client-container.test.ts index d6b742e37f7..ba75a33e0d0 100644 --- a/extensions/signal/src/client-container.test.ts +++ b/extensions/signal/src/client-container.test.ts @@ -26,6 +26,40 @@ beforeEach(() => { wsMockState.urls = []; }); +function requireFetchCall(index = 0): [RequestInfo | URL, RequestInit] { + const call = mockFetch.mock.calls[index]; + if (!call) { + throw new Error(`expected fetch call ${index}`); + } + return call as [RequestInfo | URL, RequestInit]; +} + +function expectFetchCall(index: number, url: string, method?: string): RequestInit { + const [actualUrl, init] = requireFetchCall(index); + expect(actualUrl).toBe(url); + if (method) { + expect(init.method).toBe(method); + } + return init; +} + +function expectFirstFetchCall(url: string, method?: string): RequestInit { + return expectFetchCall(0, url, method); +} + +function parseFetchBody(index = 0): Record { + const init = requireFetchCall(index)[1]; + if (typeof init.body !== "string") { + throw new Error(`expected fetch call ${index} body to be a string`); + } + return JSON.parse(init.body) as Record; +} + +function expectMockLogNotContains(mock: ReturnType, expected: string): void { + const messages = mock.mock.calls.map((call) => String(call[0] ?? "")); + expect(messages.every((message) => !message.includes(expected))).toBe(true); +} + // Minimal WebSocket mock for connection-log assertions. vi.mock("ws", () => ({ default: class MockWebSocket { @@ -91,10 +125,7 @@ describe("containerCheck", () => { const result = await containerCheck("http://localhost:8080"); expect(result).toEqual({ ok: true, status: 200, error: null }); - expect(mockFetch).toHaveBeenCalledWith( - "http://localhost:8080/v1/about", - expect.objectContaining({ method: "GET" }), - ); + expectFirstFetchCall("http://localhost:8080/v1/about", "GET"); }); it("returns ok:false when /v1/about returns 404", async () => { @@ -118,14 +149,14 @@ describe("containerCheck", () => { mockFetch.mockResolvedValue({ ok: true, status: 200 }); await containerCheck("http://localhost:8080/"); - expect(mockFetch).toHaveBeenCalledWith("http://localhost:8080/v1/about", expect.anything()); + expectFirstFetchCall("http://localhost:8080/v1/about"); }); it("adds http:// prefix when missing", async () => { mockFetch.mockResolvedValue({ ok: true, status: 200 }); await containerCheck("localhost:8080"); - expect(mockFetch).toHaveBeenCalledWith("http://localhost:8080/v1/about", expect.anything()); + expectFirstFetchCall("http://localhost:8080/v1/about"); }); it("validates the receive WebSocket when an account is provided", async () => { @@ -166,13 +197,8 @@ describe("containerRestRequest", () => { const result = await containerRestRequest("/v1/about", { baseUrl: "http://localhost:8080" }); expect(result).toEqual({ version: "1.0" }); - expect(mockFetch).toHaveBeenCalledWith( - "http://localhost:8080/v1/about", - expect.objectContaining({ - method: "GET", - headers: { "Content-Type": "application/json" }, - }), - ); + const init = expectFirstFetchCall("http://localhost:8080/v1/about", "GET"); + expect(init.headers).toEqual({ "Content-Type": "application/json" }); }); it("makes POST request with body", async () => { @@ -188,15 +214,12 @@ describe("containerRestRequest", () => { recipients: ["+1234567890"], }); - expect(mockFetch).toHaveBeenCalledWith( - "http://localhost:8080/v2/send", - expect.objectContaining({ - method: "POST", - body: JSON.stringify({ - message: "test", - number: "+1234567890", - recipients: ["+1234567890"], - }), + const init = expectFirstFetchCall("http://localhost:8080/v2/send", "POST"); + expect(init.body).toBe( + JSON.stringify({ + message: "test", + number: "+1234567890", + recipients: ["+1234567890"], }), ); }); @@ -265,8 +288,7 @@ describe("containerRestRequest", () => { // The timeout is enforced via AbortController, so we verify the call was made with a signal expect(mockFetch).toHaveBeenCalled(); - const callArgs = mockFetch.mock.calls[0]; - expect(callArgs[1].signal).toBeDefined(); + expect(requireFetchCall()[1].signal).toBeDefined(); }); }); @@ -290,15 +312,12 @@ describe("containerSendMessage", () => { }); expect(result).toEqual({ timestamp: 1700000000000 }); - expect(mockFetch).toHaveBeenCalledWith( - "http://localhost:8080/v2/send", - expect.objectContaining({ - method: "POST", - body: JSON.stringify({ - message: "Hello world", - number: "+14259798283", - recipients: ["+15550001111"], - }), + const init = expectFirstFetchCall("http://localhost:8080/v2/send", "POST"); + expect(init.body).toBe( + JSON.stringify({ + message: "Hello world", + number: "+14259798283", + recipients: ["+15550001111"], }), ); }); @@ -335,12 +354,9 @@ describe("containerSendMessage", () => { textStyles: [{ start: 0, length: 4, style: "BOLD" }], }); - const callArgs = mockFetch.mock.calls[0]; - const body = JSON.parse(callArgs[1].body); - expect(body).toMatchObject({ - message: "**Bold** text", - text_mode: "styled", - }); + const body = parseFetchBody(); + expect(body.message).toBe("**Bold** text"); + expect(body.text_mode).toBe("styled"); expect(body).not.toHaveProperty("text_style"); }); @@ -441,13 +457,11 @@ describe("containerSendTyping", () => { }); expect(result).toBe(true); - expect(mockFetch).toHaveBeenCalledWith( + const init = expectFirstFetchCall( "http://localhost:8080/v1/typing-indicator/%2B14259798283", - expect.objectContaining({ - method: "PUT", - body: JSON.stringify({ recipient: "+15550001111" }), - }), + "PUT", ); + expect(init.body).toBe(JSON.stringify({ recipient: "+15550001111" })); }); it("stops typing indicator with DELETE", async () => { @@ -463,10 +477,7 @@ describe("containerSendTyping", () => { stop: true, }); - expect(mockFetch).toHaveBeenCalledWith( - expect.anything(), - expect.objectContaining({ method: "DELETE" }), - ); + expect(requireFetchCall()[1].method).toBe("DELETE"); }); }); @@ -490,8 +501,7 @@ describe("containerRpcRequest typing", () => { { baseUrl: "http://localhost:8080" }, ); - const callArgs = mockFetch.mock.calls[0]; - const body = JSON.parse(callArgs[1].body); + const body = parseFetchBody(); expect(body.recipient).toBe("group.Z3JvdXAtMTIz"); }); }); @@ -515,15 +525,12 @@ describe("containerSendReceipt", () => { }); expect(result).toBe(true); - expect(mockFetch).toHaveBeenCalledWith( - "http://localhost:8080/v1/receipts/%2B14259798283", - expect.objectContaining({ - method: "POST", - body: JSON.stringify({ - recipient: "+15550001111", - timestamp: 1700000000000, - receipt_type: "read", - }), + const init = expectFirstFetchCall("http://localhost:8080/v1/receipts/%2B14259798283", "POST"); + expect(init.body).toBe( + JSON.stringify({ + recipient: "+15550001111", + timestamp: 1700000000000, + receipt_type: "read", }), ); }); @@ -542,8 +549,7 @@ describe("containerSendReceipt", () => { type: "viewed", }); - const callArgs = mockFetch.mock.calls[0]; - const body = JSON.parse(callArgs[1].body); + const body = parseFetchBody(); expect(body.receipt_type).toBe("viewed"); }); }); @@ -566,10 +572,7 @@ describe("containerFetchAttachment", () => { }); expect(result).toBeInstanceOf(Buffer); - expect(mockFetch).toHaveBeenCalledWith( - "http://localhost:8080/v1/attachments/attachment-123", - expect.objectContaining({ method: "GET" }), - ); + expectFirstFetchCall("http://localhost:8080/v1/attachments/attachment-123", "GET"); }); it("returns null on non-ok response", async () => { @@ -596,10 +599,7 @@ describe("containerFetchAttachment", () => { baseUrl: "http://localhost:8080", }); - expect(mockFetch).toHaveBeenCalledWith( - "http://localhost:8080/v1/attachments/path%2Fwith%2Fslashes", - expect.anything(), - ); + expectFirstFetchCall("http://localhost:8080/v1/attachments/path%2Fwith%2Fslashes"); }); it("rejects attachments above the content-length cap", async () => { @@ -661,17 +661,14 @@ describe("normalizeBaseUrl edge cases", () => { mockFetch.mockResolvedValue({ ok: true, status: 200 }); await containerCheck("https://signal.example.com"); - expect(mockFetch).toHaveBeenCalledWith( - "https://signal.example.com/v1/about", - expect.anything(), - ); + expectFirstFetchCall("https://signal.example.com/v1/about"); }); it("handles URLs with ports", async () => { mockFetch.mockResolvedValue({ ok: true, status: 200 }); await containerCheck("http://192.168.1.100:9922"); - expect(mockFetch).toHaveBeenCalledWith("http://192.168.1.100:9922/v1/about", expect.anything()); + expectFirstFetchCall("http://192.168.1.100:9922/v1/about"); }); it("rejects base URLs with credentials", async () => { @@ -698,10 +695,7 @@ describe("containerRestRequest edge cases", () => { "DELETE", ); - expect(mockFetch).toHaveBeenCalledWith( - "http://localhost:8080/v1/some-resource/123", - expect.objectContaining({ method: "DELETE" }), - ); + expectFirstFetchCall("http://localhost:8080/v1/some-resource/123", "DELETE"); }); it("handles error response with empty body", async () => { @@ -748,8 +742,8 @@ describe("streamContainerEvents", () => { expect(log).toHaveBeenCalledWith( "[signal-ws] connecting to ws://localhost:8080/v1/receive/", ); - expect(log).not.toHaveBeenCalledWith(expect.stringContaining("+14259798283")); - expect(log).not.toHaveBeenCalledWith(expect.stringContaining("%2B14259798283")); + expectMockLogNotContains(log, "+14259798283"); + expectMockLogNotContains(log, "%2B14259798283"); }); it("removes the abort listener when the stream closes", async () => { @@ -792,16 +786,13 @@ describe("containerSendReaction", () => { }); expect(result).toEqual({ timestamp: 1700000000000 }); - expect(mockFetch).toHaveBeenCalledWith( - "http://localhost:8080/v1/reactions/%2B14259798283", - expect.objectContaining({ - method: "POST", - body: JSON.stringify({ - recipient: "+15550001111", - reaction: "👍", - target_author: "+15550001111", - timestamp: 1699999999999, - }), + const init = expectFirstFetchCall("http://localhost:8080/v1/reactions/%2B14259798283", "POST"); + expect(init.body).toBe( + JSON.stringify({ + recipient: "+15550001111", + reaction: "👍", + target_author: "+15550001111", + timestamp: 1699999999999, }), ); }); @@ -823,8 +814,7 @@ describe("containerSendReaction", () => { groupId: "group-123", }); - const callArgs = mockFetch.mock.calls[0]; - const body = JSON.parse(callArgs[1].body); + const body = parseFetchBody(); expect(body.group_id).toBe("group-123"); }); }); @@ -854,8 +844,7 @@ describe("containerRpcRequest reactions", () => { { baseUrl: "http://localhost:8080" }, ); - const callArgs = mockFetch.mock.calls[0]; - const body = JSON.parse(callArgs[1].body); + const body = parseFetchBody(); expect(body.recipient).toBe("group.Z3JvdXAtMTIz"); expect(body.group_id).toBe("group.Z3JvdXAtMTIz"); expect(body.target_author).toBe("author-uuid"); @@ -884,16 +873,16 @@ describe("containerRemoveReaction", () => { }); expect(result).toEqual({ timestamp: 1700000000000 }); - expect(mockFetch).toHaveBeenCalledWith( + const init = expectFirstFetchCall( "http://localhost:8080/v1/reactions/%2B14259798283", - expect.objectContaining({ - method: "DELETE", - body: JSON.stringify({ - recipient: "+15550001111", - reaction: "👍", - target_author: "+15550001111", - timestamp: 1699999999999, - }), + "DELETE", + ); + expect(init.body).toBe( + JSON.stringify({ + recipient: "+15550001111", + reaction: "👍", + target_author: "+15550001111", + timestamp: 1699999999999, }), ); }); @@ -915,8 +904,7 @@ describe("containerRemoveReaction", () => { groupId: "group-123", }); - const callArgs = mockFetch.mock.calls[0]; - const body = JSON.parse(callArgs[1].body); + const body = parseFetchBody(); expect(body.group_id).toBe("group-123"); }); });