import {test} from 'node:test'; import assert from 'node:assert/strict'; import {createHash,randomBytes} from 'node:crypto'; import {createSha256} from '../../src/lib/canvas-runtime/sha256.ts'; import {importSourceDigest} from '../../src/lib/canvas-runtime/import-integrity.ts'; const expected=bytes=>createHash('sha256').update(bytes).digest('hex'); test('incremental SHA-256 matches known vectors, every padding boundary and varied chunk splits',()=>{ for(const bytes of [Buffer.alloc(0),Buffer.from('abc'),Buffer.from('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'),Buffer.alloc(1000000,97),...Array.from({length:260},(_,n)=>randomBytes(n))]){ for(const chunk of [1,7,63,64,65,8192]){const hash=createSha256();for(let offset=0;offsethash.update(new Uint8Array()),/digest_finalized/);} } }); test('source digest reads bounded slices, yields and never materializes the entire Blob',async()=>{ const data=randomBytes(1024*1024+13),reads=[];let yields=0; class SlicedBlob extends Blob{arrayBuffer(){throw Error('whole Blob read');}slice(start,end){reads.push(end-start);return super.slice(start,end);}} assert.equal(await importSourceDigest(new SlicedBlob([data]),undefined,{chunkBytes:65536,yieldTask:async()=>{yields++;}}),expected(data));assert.equal(reads.length,17);assert.ok(reads.every(size=>size<=65536));assert.equal(yields,16); }); test('source digest cancellation fences a late chunk and a scheduler yield',async()=>{ let release,entered;const gate=new Promise(resolve=>release=resolve),ready=new Promise(resolve=>entered=resolve),controller=new AbortController();class Chunk extends Blob{async arrayBuffer(){entered();await gate;return super.arrayBuffer();}}class Source extends Blob{slice(...args){return new Chunk([super.slice(...args)]);}} const pending=importSourceDigest(new Source(['abc']),controller.signal);await ready;controller.abort();release();await assert.rejects(pending,/import_aborted/);const second=new AbortController();await assert.rejects(importSourceDigest(new Blob(['abcdef']),second.signal,{chunkBytes:2,yieldTask:async()=>second.abort()}),/import_aborted/); });