local-modules.test.mjs (4261B)
1 import test from 'node:test'; 2 import assert from 'node:assert/strict'; 3 import {makeBundle,sizedBundle} from './bundle-fixture.mjs'; 4 import {decodeBundle,sha256} from '../dist/bundle.js'; 5 import {encodeModule,moduleSnapshot} from '../dist/module-bundle.js'; 6 import {validateLocalModule,localModuleInputs} from '../dist/local-modules.js'; 7 import {moduleIdentity} from '../dist/module-identity.js'; 8 import {moduleDependencies} from '../dist/module-stage.js'; 9 10 test('local modules validate complete interfaces and distinct manifest/bundle identities',async()=>{ 11 const bytes=makeBundle([[],[0],[0,1]],[['left',2],['right',1]]); 12 const local=await validateLocalModule(bytes); 13 assert.equal(local.hash,await sha256(bytes));assert.equal(local.manifest,await moduleIdentity(decodeBundle(bytes))); 14 assert.notEqual(local.hash,local.manifest);assert.deepEqual(local.names,['left','right']); 15 bytes[0]=0;assert.equal(local.bytes[0],65,'owned immutable byte snapshot'); 16 await assert.rejects(()=>validateLocalModule(bytes),/magic/); 17 await assert.rejects(()=>validateLocalModule(makeBundle([[]],[['not a name',0]])),/interface/); 18 await assert.rejects(()=>validateLocalModule(makeBundle([[]],[['x',0]],{evaluation:'other'})),/semantics/); 19 await assert.rejects(()=>validateLocalModule(new Uint8Array(16777217)),/16 MiB/); 20 }); 21 test('local-first resolution is offline and never falls back after corrupt local input',async()=>{ 22 const entry=await validateLocalModule(makeBundle([[],[0]],[['value',1]])); 23 const stage={native:true,body:'',imports:[[entry.manifest,'L']],exports:[]}; 24 const fetch=globalThis.fetch;globalThis.fetch=()=>{throw Error('Unexpected network access');}; 25 try{ 26 const modules=await moduleDependencies(new URL('https://invalid.test/'),stage,[entry]); 27 assert.deepEqual([...modules.keys()],[entry.manifest]); 28 await assert.rejects(()=>moduleDependencies(new URL('https://invalid.test/'),stage,[{...entry,bytes:makeBundle([[]],[['value',0]])}]),/manifest mismatch/); 29 await assert.rejects(()=>moduleDependencies(new URL('https://invalid.test/'),stage,[{...entry,bytes:new Uint8Array([0])}]),/Truncated/); 30 }finally{globalThis.fetch=fetch;} 31 }); 32 test('local collection transfer bounds, duplicates and copies',async()=>{ 33 const entry=await validateLocalModule(makeBundle([[]])); 34 const inputs=localModuleInputs([entry]);entry.bytes[0]=0;assert.equal(inputs[0].bytes[0],65); 35 assert.throws(()=>localModuleInputs([entry,entry]),/Duplicate/); 36 assert.throws(()=>localModuleInputs(Array(33).fill(entry)),/count/); 37 assert.throws(()=>localModuleInputs([{manifest:'x',bytes:entry.bytes}]),/hash/); 38 assert.throws(()=>localModuleInputs([{...entry,bytes:[]}]),/byte/); 39 const large=sizedBundle(16777216); 40 assert.throws(()=>localModuleInputs(Array.from({length:5},(_,i)=>({manifest:String(i).repeat(64),bytes:large}))),/64 MiB/); 41 }); 42 test('portable module encoding canonicalizes duplicates without unfolding shared branches',async()=>{ 43 const nodes=[{kind:'leaf'},{kind:'leaf'},{kind:'stem',child:1},{kind:'stem',child:0}]; 44 for(let i=4;i<44;i++)nodes.push({kind:'fork',left:i-1,right:i-1}); 45 const bundle={nodes,exports:[{name:'deep',root:43},{name:'alias',root:43},{name:'small',root:2}],roots:[43],metadata:{}}; 46 const decoded=decodeBundle(encodeModule(bundle)); 47 assert.equal(decoded.nodes.length,42);assert.equal(decoded.exports[0].root,decoded.exports[1].root); 48 assert.equal(decoded.exports[2].root,1); 49 assert.ok(encodeModule(bundle).length<2000); 50 assert.deepEqual(encodeModule(decoded),encodeModule(bundle)); 51 assert.throws(()=>encodeModule({...bundle,exports:[{name:'duplicate',root:0},{name:'duplicate',root:1}]}),/duplicate/); 52 }); 53 test('module snapshots select public roots, drop synthetic list spine and refuse partial snapshots',()=>{ 54 const snapshot={root:5,aliases:[],truncated:false,nodes:[ 55 {id:0,kind:'leaf',children:[]},{id:1,kind:'stem',children:[0]}, 56 {id:3,kind:'fork',children:[1,0]},{id:5,kind:'fork',children:[1,3]}]}; 57 const bundle=decodeBundle(moduleSnapshot(['one','two'],snapshot)); 58 assert.equal(bundle.nodes.length,2);assert.deepEqual(bundle.exports.map(e=>e.root),[1,1]); 59 assert.throws(()=>moduleSnapshot(['one'],snapshot),/tail/); 60 assert.throws(()=>moduleSnapshot(['one','two'],{...snapshot,truncated:true}),/partial/); 61 });