
























@@ -61,6 +61,62 @@ function fetchAttachment(params: Parameters<typeof fetchAttachmentImpl>[0]) {
6161return fetchAttachmentImpl({ ...params, apiMode: currentApiMode });
6262}
636364+type MockCalls = {
65+mock: { calls: unknown[][] };
66+};
67+68+function isRecord(value: unknown): value is Record<string, unknown> {
69+return typeof value === "object" && value !== null;
70+}
71+72+function requireRecord(value: unknown, label: string): Record<string, unknown> {
73+expect(isRecord(value), `${label} should be an object`).toBe(true);
74+if (!isRecord(value)) {
75+throw new Error(`${label} should be an object`);
76+}
77+return value;
78+}
79+80+function expectFields(record: Record<string, unknown>, expected: Record<string, unknown>) {
81+for (const [key, value] of Object.entries(expected)) {
82+expect(record[key], key).toEqual(value);
83+}
84+}
85+86+function expectRpcCall(params: {
87+mock: MockCalls;
88+method: string;
89+rpcParams?: Record<string, unknown>;
90+options?: Record<string, unknown>;
91+}) {
92+expect(params.mock.mock.calls).toHaveLength(1);
93+const [method, rpcParams, options] = params.mock.mock.calls[0] ?? [];
94+expect(method).toBe(params.method);
95+if (params.rpcParams) {
96+expectFields(requireRecord(rpcParams, "rpc params"), params.rpcParams);
97+} else {
98+expect(rpcParams).toBeDefined();
99+}
100+if (params.options) {
101+expectFields(requireRecord(options, "rpc options"), params.options);
102+} else {
103+expect(options).toBeDefined();
104+}
105+}
106+107+function expectSingleObjectCall(mock: MockCalls, expected: Record<string, unknown>) {
108+expect(mock.mock.calls).toHaveLength(1);
109+const [payload] = mock.mock.calls[0] ?? [];
110+expectFields(requireRecord(payload, "call payload"), expected);
111+}
112+113+function expectContainerFetchCall(expected: Record<string, unknown>) {
114+expect(mockContainerFetchAttachment.mock.calls).toHaveLength(1);
115+const [attachmentId, options] = mockContainerFetchAttachment.mock.calls[0] ?? [];
116+expect(attachmentId).toBe("attachment-123");
117+expectFields(requireRecord(options, "container fetch options"), expected);
118+}
119+64120describe("detectSignalApiMode", () => {
65121beforeEach(() => {
66122vi.clearAllMocks();
@@ -160,11 +216,12 @@ describe("signalRpcRequest", () => {
160216);
161217162218expect(result).toEqual({ timestamp: 1700000000000 });
163-expect(mockNativeRpcRequest).toHaveBeenCalledWith(
164-"send",
165-expect.objectContaining({ message: "Hello" }),
166-expect.objectContaining({ baseUrl: "http://localhost:8080" }),
167-);
219+expectRpcCall({
220+mock: mockNativeRpcRequest,
221+method: "send",
222+rpcParams: { message: "Hello" },
223+options: { baseUrl: "http://localhost:8080" },
224+});
168225expect(mockContainerRpcRequest).not.toHaveBeenCalled();
169226});
170227@@ -179,11 +236,12 @@ describe("signalRpcRequest", () => {
179236);
180237181238expect(result).toEqual({ timestamp: 1700000000000 });
182-expect(mockContainerRpcRequest).toHaveBeenCalledWith(
183-"send",
184-expect.objectContaining({ message: "Hello" }),
185-expect.objectContaining({ baseUrl: "http://localhost:8080" }),
186-);
239+expectRpcCall({
240+mock: mockContainerRpcRequest,
241+method: "send",
242+rpcParams: { message: "Hello" },
243+options: { baseUrl: "http://localhost:8080" },
244+});
187245expect(mockNativeRpcRequest).not.toHaveBeenCalled();
188246});
189247@@ -210,11 +268,7 @@ describe("signalRpcRequest", () => {
210268{ account: "+1", recipient: ["+2"] },
211269{ baseUrl: "http://localhost:8080" },
212270);
213-expect(mockNativeRpcRequest).toHaveBeenCalledWith(
214-"sendTyping",
215-expect.anything(),
216-expect.anything(),
217-);
271+expectRpcCall({ mock: mockNativeRpcRequest, method: "sendTyping" });
218272});
219273220274it("passes all RPC methods through to container", async () => {
@@ -226,11 +280,7 @@ describe("signalRpcRequest", () => {
226280{ account: "+1", recipient: ["+2"] },
227281{ baseUrl: "http://localhost:8080" },
228282);
229-expect(mockContainerRpcRequest).toHaveBeenCalledWith(
230-"sendReceipt",
231-expect.anything(),
232-expect.anything(),
233-);
283+expectRpcCall({ mock: mockContainerRpcRequest, method: "sendReceipt" });
234284});
235285});
236286@@ -309,12 +359,10 @@ describe("streamSignalEvents", () => {
309359 onEvent,
310360});
311361312-expect(mockNativeStreamEvents).toHaveBeenCalledWith(
313-expect.objectContaining({
314-baseUrl: "http://localhost:8080",
315-account: "+14259798283",
316-}),
317-);
362+expectSingleObjectCall(mockNativeStreamEvents, {
363+baseUrl: "http://localhost:8080",
364+account: "+14259798283",
365+});
318366expect(mockStreamContainerEvents).not.toHaveBeenCalled();
319367});
320368@@ -329,12 +377,10 @@ describe("streamSignalEvents", () => {
329377 onEvent,
330378});
331379332-expect(mockStreamContainerEvents).toHaveBeenCalledWith(
333-expect.objectContaining({
334-baseUrl: "http://localhost:8080",
335-account: "+14259798283",
336-}),
337-);
380+expectSingleObjectCall(mockStreamContainerEvents, {
381+baseUrl: "http://localhost:8080",
382+account: "+14259798283",
383+});
338384expect(mockNativeStreamEvents).not.toHaveBeenCalled();
339385});
340386@@ -383,11 +429,9 @@ describe("streamSignalEvents", () => {
383429onEvent: vi.fn(),
384430});
385431386-expect(mockNativeStreamEvents).toHaveBeenCalledWith(
387-expect.objectContaining({
388-abortSignal: abortController.signal,
389-}),
390-);
432+expectSingleObjectCall(mockNativeStreamEvents, {
433+abortSignal: abortController.signal,
434+});
391435});
392436393437it("forwards timeout to native SSE stream", async () => {
@@ -399,11 +443,9 @@ describe("streamSignalEvents", () => {
399443onEvent: vi.fn(),
400444});
401445402-expect(mockNativeStreamEvents).toHaveBeenCalledWith(
403-expect.objectContaining({
404-timeoutMs: 45000,
405-}),
406-);
446+expectSingleObjectCall(mockNativeStreamEvents, {
447+timeoutMs: 45000,
448+});
407449});
408450409451it("uses a positive probe timeout while preserving zero stream timeout", async () => {
@@ -425,11 +467,9 @@ describe("streamSignalEvents", () => {
42546710000,
426468"+14259798283",
427469);
428-expect(mockNativeStreamEvents).toHaveBeenCalledWith(
429-expect.objectContaining({
430-timeoutMs: 0,
431-}),
432-);
470+expectSingleObjectCall(mockNativeStreamEvents, {
471+timeoutMs: 0,
472+});
433473});
434474435475it("forwards timeout to container event stream", async () => {
@@ -442,11 +482,9 @@ describe("streamSignalEvents", () => {
442482onEvent: vi.fn(),
443483});
444484445-expect(mockStreamContainerEvents).toHaveBeenCalledWith(
446-expect.objectContaining({
447-timeoutMs: 45000,
448-}),
449-);
485+expectSingleObjectCall(mockStreamContainerEvents, {
486+timeoutMs: 45000,
487+});
450488});
451489452490it("revalidates an unvalidated cached container mode before streaming", async () => {
@@ -499,15 +537,15 @@ describe("fetchAttachment", () => {
499537});
500538501539expect(result).toBeInstanceOf(Buffer);
502-expect(mockNativeRpcRequest).toHaveBeenCalledWith(
503-"getAttachment",
504-expect.objectContaining({
540+expectRpcCall({
541+mock: mockNativeRpcRequest,
542+method: "getAttachment",
543+rpcParams: {
505544id: "attachment-123",
506545account: "+14259798283",
507546recipient: "+15550001111",
508-}),
509-expect.anything(),
510-);
547+},
548+});
511549});
512550513551it("uses container REST for container mode", async () => {
@@ -521,10 +559,7 @@ describe("fetchAttachment", () => {
521559});
522560523561expect(result).toBe(mockBuffer);
524-expect(mockContainerFetchAttachment).toHaveBeenCalledWith(
525-"attachment-123",
526-expect.objectContaining({ baseUrl: "http://localhost:8080" }),
527-);
562+expectContainerFetchCall({ baseUrl: "http://localhost:8080" });
528563});
529564530565it("returns null for native mode without sender or groupId", async () => {
@@ -546,11 +581,11 @@ describe("fetchAttachment", () => {
546581groupId: "group-123",
547582});
548583549-expect(mockNativeRpcRequest).toHaveBeenCalledWith(
550-"getAttachment",
551-expect.objectContaining({ groupId: "group-123" }),
552-expect.anything(),
553-);
584+expectRpcCall({
585+mock: mockNativeRpcRequest,
586+method: "getAttachment",
587+rpcParams: { groupId: "group-123" },
588+});
554589});
555590556591it("returns null when native RPC returns no data", async () => {
@@ -590,12 +625,7 @@ describe("fetchAttachment", () => {
590625timeoutMs: 60000,
591626});
592627593-expect(mockContainerFetchAttachment).toHaveBeenCalledWith(
594-"attachment-123",
595-expect.objectContaining({
596-timeoutMs: 60000,
597-}),
598-);
628+expectContainerFetchCall({ timeoutMs: 60000 });
599629});
600630601631it("passes max response bytes to container fetch", async () => {
@@ -608,11 +638,6 @@ describe("fetchAttachment", () => {
608638maxResponseBytes: 4096,
609639});
610640611-expect(mockContainerFetchAttachment).toHaveBeenCalledWith(
612-"attachment-123",
613-expect.objectContaining({
614-maxResponseBytes: 4096,
615-}),
616-);
641+expectContainerFetchCall({ maxResponseBytes: 4096 });
617642});
618643});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。