Files
Endoleap/tests/headless/sha256.test.mjs
T

30 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 增量 SHA-256 与分块导入摘要测试,使用 Node crypto 的独立实现作为结果参照。
* 覆盖填充边界、不同 update 块宽、重复最终化读取,以及异步读块/让出时取消的迟到结果隔离。
*/
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';
// 用 Node createHash 计算参照摘要;既不复用待测算法,也不以自身输出作为预期值。
const expected=bytes=>createHash('sha256').update(bytes).digest('hex');
// 空值、标准字符串、百万个 a 及长度 0–259 的随机输入,分别按六种块宽更新;hex 可重复读取,但最终化后禁止 update。
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;offset<bytes.length;offset+=chunk)hash.update(bytes.subarray(offset,offset+chunk));assert.equal(hash.hex(),expected(bytes),`${bytes.length}/${chunk}`);assert.equal(hash.hex(),expected(bytes));assert.throws(()=>hash.update(new Uint8Array()),/digest_finalized/);}
}
});
// 1 MiB+13 字节按 64 KiB 读取为 17 块并让出 16 次;摘要匹配 Node,且没有整块源 Blob 读取。
test('source digest reads bounded slices, yields and never materializes the entire Blob',async()=>{
const data=randomBytes(1024*1024+13),reads=[];let yields=0;
// 禁止对整个源 Blob 调 arrayBuffer,slice 记录请求块长并交给真实 Blob 读字节。
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);
});
// 分别在读块挂起时 abort 和 yield 回调内 abort;迟到块及调度返回后都不能完成摘要,统一拒绝 import_aborted。
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();/* 读出的分块再套一层 gate,保证 abort 发生在 arrayBuffer 真正返回之前。 */ class Chunk extends Blob{async arrayBuffer(){entered();await gate;return super.arrayBuffer();}}/* 保留真实 slice 字节,但让每一块都经过上述可暂停读取。 */ 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/);
});