From 8e1a6b53d445afce2797759bd6614f2b7aa672f5 Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Fri, 28 Aug 2026 16:53:14 +0800 Subject: [PATCH] fix: node-20-safe browser-host simulation in the Cache API test undici on node 20 splits process.versions.node inside fetch(); deleting it crashed the test host. Swap the globalThis.process reference instead - only the registry's detection reads globalThis. --- test/imf.test.ts | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/test/imf.test.ts b/test/imf.test.ts index fafa915..f8efb7b 100644 --- a/test/imf.test.ts +++ b/test/imf.test.ts @@ -195,15 +195,18 @@ describe("registry", () => { const indexUrl = `http://127.0.0.1:${port}/index.yaml` indexBody = `version: 1\nmodels:\n tiny-1.0:\n filename: tiny.zip\n url: http://127.0.0.1:${port}/tiny.zip\n sha256: ${sha}\n` - const store = new Map() + // store raw bytes: Response.clone() stream semantics differ across + // undici versions (node 20 vs 24) and are irrelevant to the contract + const store = new Map() const fakeCache = { async match(req: RequestInfo) { const key = String(req instanceof Request ? req.url : req) - return store.get(key)?.clone() + const bytes = store.get(key) + return bytes === undefined ? undefined : new Response(bytes) }, async put(req: RequestInfo, res: Response) { const key = String(req instanceof Request ? req.url : req) - store.set(key, res.clone()) + store.set(key, new Uint8Array(await res.arrayBuffer())) }, async delete(req: RequestInfo) { const key = String(req instanceof Request ? req.url : req) @@ -212,29 +215,38 @@ describe("registry", () => { } const g = globalThis as Record g["caches"] = { open: async () => fakeCache } - // simulate a browser host: no Node fs, so the Cache API path runs - const versions = process.versions as { node?: string } - const realNode = versions.node - delete versions.node + // Simulate a browser host by swapping the globalThis.process + // reference (the registry detects Node via globalThis). Deleting + // process.versions.node instead breaks node 20's bundled undici, + // which splits it inside fetch(). + const realProcess = globalThis.process + const withBrowserHost = async (fn: () => Promise): Promise => { + const gg = globalThis as { process?: unknown } + gg.process = { env: {}, versions: {} } + try { + return await fn() + } finally { + gg.process = realProcess + } + } try { process.env["SECRYST_CACHE"] = undefined - const first = await resolve("tiny-1.0", indexUrl) + const first = await withBrowserHost(() => resolve("tiny-1.0", indexUrl)) expect([...first.bytes]).toEqual([...fixtureZip]) expect(store.size).toBe(1) // channel dies; the cached copy serves, still sha-verified channelUp = false - const second = await resolve("tiny-1.0", indexUrl) + const second = await withBrowserHost(() => resolve("tiny-1.0", indexUrl)) expect([...second.bytes]).toEqual([...fixtureZip]) // a corrupted cache entry falls through to a fresh download channelUp = true const key = store.keys().next().value as string - store.set(key, new Response(new Uint8Array([1, 2, 3]))) - const third = await resolve("tiny-1.0", indexUrl) + store.set(key, new Uint8Array([1, 2, 3])) + const third = await withBrowserHost(() => resolve("tiny-1.0", indexUrl)) expect([...third.bytes]).toEqual([...fixtureZip]) } finally { - if (realNode !== undefined) versions.node = realNode delete g["caches"] delete process.env["SECRYST_CACHE"] await new Promise((r) => server.close(() => r()))