import {test} from 'node:test';import assert from 'node:assert/strict';import {createHash} from 'node:crypto'; import {digestWorkerSource,nativeDigestByteLimit} from '../../src/lib/canvas-runtime/digest-worker-source.ts'; test('native Worker hashing and streaming fallback produce identical SHA256 for empty, UTF8 and binary sources',async()=>{ for(const bytes of [new Blob(),new Blob(['δΈ­ζ–‡ πŸ˜€']),new Blob([new Uint8Array([0,255,128,10])])]){ let calls=0;const native={digest(algorithm,input){calls++;assert.equal(algorithm,'SHA-256');assert.equal(input.byteLength,bytes.size);return crypto.subtle.digest(algorithm,input);}}; assert.equal(await digestWorkerSource(bytes,native),await digestWorkerSource(bytes,null));assert.equal(calls,1); } }); test('native read or digest failures stay visible and never silently retry through the streaming fallback',async()=>{ class BrokenRead extends Blob{arrayBuffer(){return Promise.reject(Error('read failed'));}slice(){throw Error('unexpected fallback');}} await assert.rejects(digestWorkerSource(new BrokenRead(['input'])),/read failed/); const bytes=new Blob(['input']);await assert.rejects(digestWorkerSource(bytes,{digest(){throw Error('native failed');}}),/native failed/);await assert.rejects(digestWorkerSource(bytes,{async digest(){return new ArrayBuffer(1);}}),/invalid_digest_response/); }); test('inputs above the native allocation budget use bounded chunks and never request a whole buffer',async()=>{ const size=nativeDigestByteLimit+1,hash=createHash('sha256');let largest=0; class ChunkOnly extends Blob{get size(){return size;}arrayBuffer(){throw Error('unbounded read');}slice(start=0,end=size){const count=Math.max(0,Math.min(size,end)-start);largest=Math.max(largest,count);return new Blob([new Uint8Array(count)]);}} for(let cursor=0;cursor