32 lines
3.6 KiB
JavaScript
32 lines
3.6 KiB
JavaScript
/**
|
||
* 增量 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。
|
||
// 六种切分覆盖 64 字节块的前后边界及多块更新;随机内容每次不同,预期摘要始终由 Node crypto 独立计算。
|
||
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);}}
|
||
// 最后一块只有 13 字节;slice 的请求长度不超过上限,并且只在块与块之间让出执行权。
|
||
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/);
|
||
});
|