fix(rpc): isolate integration test from cross-suite fetch pollution
packages/csr's actions.test.ts and reactive.test.ts both leave globalThis.fetch mutated across bun test files (reactive.test.ts's 'cache invalidation refetches...' test replaces it and never restores it). Since bun test runs files sequentially rather than importing all of them up front, a module-level capture of fetch in this file would already observe csr's leftover mock (csr sorts before rpc). Route the real-socket assertion through a small node:http-backed fetch implementation instead of relying on globalThis.fetch at all, keeping the test's actual target - httpTransport()'s default resolveOrigin - unaffected by any other suite's global mutation.
This commit is contained in:
@@ -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<Response> {
|
||||
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<string, string>,
|
||||
},
|
||||
(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",
|
||||
|
||||
Reference in New Issue
Block a user