@@ -195,15 +195,18 @@ describe("registry", () => {
195195 const indexUrl = `http://127.0.0.1:${ port } /index.yaml`
196196 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`
197197
198- const store = new Map < string , Response > ( )
198+ // store raw bytes: Response.clone() stream semantics differ across
199+ // undici versions (node 20 vs 24) and are irrelevant to the contract
200+ const store = new Map < string , Uint8Array > ( )
199201 const fakeCache = {
200202 async match ( req : RequestInfo ) {
201203 const key = String ( req instanceof Request ? req . url : req )
202- return store . get ( key ) ?. clone ( )
204+ const bytes = store . get ( key )
205+ return bytes === undefined ? undefined : new Response ( bytes )
203206 } ,
204207 async put ( req : RequestInfo , res : Response ) {
205208 const key = String ( req instanceof Request ? req . url : req )
206- store . set ( key , res . clone ( ) )
209+ store . set ( key , new Uint8Array ( await res . arrayBuffer ( ) ) )
207210 } ,
208211 async delete ( req : RequestInfo ) {
209212 const key = String ( req instanceof Request ? req . url : req )
@@ -212,29 +215,38 @@ describe("registry", () => {
212215 }
213216 const g = globalThis as Record < string , unknown >
214217 g [ "caches" ] = { open : async ( ) => fakeCache }
215- // simulate a browser host: no Node fs, so the Cache API path runs
216- const versions = process . versions as { node ?: string }
217- const realNode = versions . node
218- delete versions . node
218+ // Simulate a browser host by swapping the globalThis.process
219+ // reference (the registry detects Node via globalThis). Deleting
220+ // process.versions.node instead breaks node 20's bundled undici,
221+ // which splits it inside fetch().
222+ const realProcess = globalThis . process
223+ const withBrowserHost = async < T > ( fn : ( ) => Promise < T > ) : Promise < T > => {
224+ const gg = globalThis as { process ?: unknown }
225+ gg . process = { env : { } , versions : { } }
226+ try {
227+ return await fn ( )
228+ } finally {
229+ gg . process = realProcess
230+ }
231+ }
219232 try {
220233 process . env [ "SECRYST_CACHE" ] = undefined
221- const first = await resolve ( "tiny-1.0" , indexUrl )
234+ const first = await withBrowserHost ( ( ) => resolve ( "tiny-1.0" , indexUrl ) )
222235 expect ( [ ...first . bytes ] ) . toEqual ( [ ...fixtureZip ] )
223236 expect ( store . size ) . toBe ( 1 )
224237
225238 // channel dies; the cached copy serves, still sha-verified
226239 channelUp = false
227- const second = await resolve ( "tiny-1.0" , indexUrl )
240+ const second = await withBrowserHost ( ( ) => resolve ( "tiny-1.0" , indexUrl ) )
228241 expect ( [ ...second . bytes ] ) . toEqual ( [ ...fixtureZip ] )
229242
230243 // a corrupted cache entry falls through to a fresh download
231244 channelUp = true
232245 const key = store . keys ( ) . next ( ) . value as string
233- store . set ( key , new Response ( new Uint8Array ( [ 1 , 2 , 3 ] ) ) )
234- const third = await resolve ( "tiny-1.0" , indexUrl )
246+ store . set ( key , new Uint8Array ( [ 1 , 2 , 3 ] ) )
247+ const third = await withBrowserHost ( ( ) => resolve ( "tiny-1.0" , indexUrl ) )
235248 expect ( [ ...third . bytes ] ) . toEqual ( [ ...fixtureZip ] )
236249 } finally {
237- if ( realNode !== undefined ) versions . node = realNode
238250 delete g [ "caches" ]
239251 delete process . env [ "SECRYST_CACHE" ]
240252 await new Promise < void > ( ( r ) => server . close ( ( ) => r ( ) ) )
0 commit comments