test: clear signal client container broad matchers

This commit is contained in:
Peter Steinberger
2026-05-10 16:05:49 +01:00
parent e0ffbce7e0
commit b5633698e2

View File

@@ -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<string, unknown> {
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<string, unknown>;
}
function expectMockLogNotContains(mock: ReturnType<typeof vi.fn>, 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/<redacted>",
);
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");
});
});