|
9 | 9 | installBlueBubblesFetchTestHooks, |
10 | 10 | mockBlueBubblesPrivateApiStatusOnce, |
11 | 11 | } from "./test-harness.js"; |
12 | | -import type { BlueBubblesSendTarget } from "./types.js"; |
| 12 | +import { _setFetchGuardForTesting, type BlueBubblesSendTarget } from "./types.js"; |
13 | 13 |
|
14 | 14 | const mockFetch = vi.fn(); |
15 | 15 | const privateApiStatusMock = vi.mocked(getCachedBlueBubblesPrivateApiStatus); |
@@ -61,6 +61,33 @@ function mockNewChatSendResponse(guid: string) { |
61 | 61 | }); |
62 | 62 | } |
63 | 63 |
|
| 64 | +function installSsrFPolicyCapture(policies: unknown[]) { |
| 65 | + _setFetchGuardForTesting(async (params) => { |
| 66 | + policies.push(params.policy); |
| 67 | + const raw = await globalThis.fetch(params.url, params.init); |
| 68 | + let body: ArrayBuffer; |
| 69 | + if (typeof raw.arrayBuffer === "function") { |
| 70 | + body = await raw.arrayBuffer(); |
| 71 | + } else { |
| 72 | + const text = |
| 73 | + typeof (raw as { text?: () => Promise<string> }).text === "function" |
| 74 | + ? await (raw as { text: () => Promise<string> }).text() |
| 75 | + : typeof (raw as { json?: () => Promise<unknown> }).json === "function" |
| 76 | + ? JSON.stringify(await (raw as { json: () => Promise<unknown> }).json()) |
| 77 | + : ""; |
| 78 | + body = new TextEncoder().encode(text).buffer; |
| 79 | + } |
| 80 | + return { |
| 81 | + response: new Response(body, { |
| 82 | + status: (raw as { status?: number }).status ?? 200, |
| 83 | + headers: (raw as { headers?: HeadersInit }).headers, |
| 84 | + }), |
| 85 | + release: async () => {}, |
| 86 | + finalUrl: params.url, |
| 87 | + }; |
| 88 | + }); |
| 89 | +} |
| 90 | + |
64 | 91 | describe("send", () => { |
65 | 92 | describe("resolveChatGuidForTarget", () => { |
66 | 93 | const resolveHandleTargetGuid = async (data: Array<Record<string, unknown>>) => { |
@@ -448,6 +475,44 @@ describe("send", () => { |
448 | 475 | expect(body.method).toBeUndefined(); |
449 | 476 | }); |
450 | 477 |
|
| 478 | + it("auto-enables private-network fetches for loopback serverUrl when allowPrivateNetwork is not set", async () => { |
| 479 | + const policies: unknown[] = []; |
| 480 | + installSsrFPolicyCapture(policies); |
| 481 | + mockResolvedHandleTarget(); |
| 482 | + mockSendResponse({ data: { guid: "msg-loopback" } }); |
| 483 | + |
| 484 | + try { |
| 485 | + const result = await sendMessageBlueBubbles("+15551234567", "Hello world!", { |
| 486 | + serverUrl: "http://localhost:1234", |
| 487 | + password: "test", |
| 488 | + }); |
| 489 | + |
| 490 | + expect(result.messageId).toBe("msg-loopback"); |
| 491 | + expect(policies).toEqual([{ allowPrivateNetwork: true }, { allowPrivateNetwork: true }]); |
| 492 | + } finally { |
| 493 | + _setFetchGuardForTesting(null); |
| 494 | + } |
| 495 | + }); |
| 496 | + |
| 497 | + it("auto-enables private-network fetches for private IP serverUrl when allowPrivateNetwork is not set", async () => { |
| 498 | + const policies: unknown[] = []; |
| 499 | + installSsrFPolicyCapture(policies); |
| 500 | + mockResolvedHandleTarget(); |
| 501 | + mockSendResponse({ data: { guid: "msg-private-ip" } }); |
| 502 | + |
| 503 | + try { |
| 504 | + const result = await sendMessageBlueBubbles("+15551234567", "Hello world!", { |
| 505 | + serverUrl: "http://192.168.1.5:1234", |
| 506 | + password: "test", |
| 507 | + }); |
| 508 | + |
| 509 | + expect(result.messageId).toBe("msg-private-ip"); |
| 510 | + expect(policies).toEqual([{ allowPrivateNetwork: true }, { allowPrivateNetwork: true }]); |
| 511 | + } finally { |
| 512 | + _setFetchGuardForTesting(null); |
| 513 | + } |
| 514 | + }); |
| 515 | + |
451 | 516 | it("strips markdown formatting from outbound messages", async () => { |
452 | 517 | mockResolvedHandleTarget(); |
453 | 518 | mockSendResponse({ data: { guid: "msg-uuid-stripped" } }); |
|
0 commit comments