diff --git a/packages/rpc/test/integration.test.ts b/packages/rpc/test/integration.test.ts index c89e1ec3..77d28dbb 100644 --- a/packages/rpc/test/integration.test.ts +++ b/packages/rpc/test/integration.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, test } from "bun:test"; +import * as http from "node:http"; import type { Context } from "@wrnexus/core"; import { v } from "@wrnexus/validation"; import { serviceClient } from "../src/client.ts"; @@ -70,6 +71,50 @@ function httpWire(allowed: boolean) { }); } +// packages/csr's actions.test.ts deletes globalThis.fetch (and restores its +// own captured copy) around each of its tests, and packages/csr's +// reactive.test.ts's "cache invalidation refetches matching client Async +// boundaries" test replaces globalThis.fetch with a mock and never restores +// it at all. bun test runs test files sequentially — importing a file and +// running its tests before moving to the next — rather than importing every +// file up front, so a module-level `const realFetch = fetch` captured here +// would already observe whatever packages/csr left behind by the time this +// file (which sorts after csr) is loaded. The variable under test below is +// `resolveOrigin`, not `fetch`, so instead of depending on the shared +// `globalThis.fetch` at all, this makes a real request over a real socket +// using node:http directly. That keeps the assertion about +// httpTransport()'s default origin resolution intact regardless of what any +// other suite does to the global `fetch` binding. +function realFetch(url: string, init: RequestInit): Promise { + return new Promise((resolve, reject) => { + const target = new URL(url); + const req = http.request( + { + hostname: target.hostname, + port: target.port, + path: `${target.pathname}${target.search}`, + method: init.method ?? "GET", + headers: init.headers as Record, + }, + (res) => { + const chunks: Buffer[] = []; + res.on("data", (chunk: Buffer) => chunks.push(chunk)); + res.on("error", reject); + res.on("end", () => { + resolve( + new Response(Buffer.concat(chunks), { + status: res.statusCode ?? 500, + }), + ); + }); + }, + ); + req.on("error", reject); + if (typeof init.body === "string") req.write(init.body); + req.end(); + }); +} + describe("RPC integration", () => { test("propagates identity and validates the callee permission", async () => { process.env.WRNEXUS_RPC_SECRET = "test-rpc-secret-at-least-32-chars-long"; @@ -151,7 +196,9 @@ describe("RPC integration", () => { }); const client = serviceClient(billing, { app: "billing", - transport: httpTransport(), // no resolveOrigin override — uses the real default + // no resolveOrigin override — uses the real default; fetch is pinned + // to the node:http-backed implementation above (see comment there). + transport: httpTransport({ fetch: realFetch as unknown as typeof fetch }), }); expect(await client.createInvoice({ amountCents: 42 })).toEqual({ invoiceId: "inv_42",