/** * 导入源全文件 SHA-256 绑定与任务完整性版本测试。新任务在绑定摘要前不能声明已解析游标或稳定映射,旧格式按明确兼容规则读取。 */ import {test} from 'node:test'; import assert from 'node:assert/strict'; import {importSourceDigest} from '../../src/lib/canvas-runtime/import-integrity.ts'; import {createImportJob,validateImportJob} from '../../src/lib/canvas-runtime/imports.ts'; // 标准 abc 向量和不同输入区分完整源摘要;预先取消及异步读取返回前取消都拒绝,不接受迟到结果。 test('source SHA256 binds complete bytes and cancellation fences both sides of asynchronous reading',async()=>{ assert.equal(await importSourceDigest(new Blob(['abc'])),'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad');assert.notEqual(await importSourceDigest(new Blob(['abc'])),await importSourceDigest(new Blob(['abd']))); const controller=new AbortController();controller.abort();await assert.rejects(importSourceDigest(new Blob(['abc']),controller.signal),/import_aborted/);let release;const gate=new Promise(resolve=>{release=resolve;}),later=new AbortController(),bytes=new class extends Blob{/* 此替身只覆写整 Blob 的 arrayBuffer;当前摘要走 slice().arrayBuffer(),故 gate 不拦截分块读取。本例断言调用后取消的拒绝,读块挂起场景另由 sha256.test.mjs 覆盖。 */ async arrayBuffer(){await gate;return super.arrayBuffer();}}(['abc']),result=importSourceDigest(bytes,later.signal);later.abort();release();await assert.rejects(result,/import_aborted/); }); // 新 job 为 v3/integrityVersion=1,未绑定摘要前拒绝进度,非法摘要/未来完整性解释器拒绝;显式旧格式及已绑定新格式仍可校验。 test('new source jobs declare integrity version and reject progress without a bound hash; legacy data remains readable',()=>{ const job=createImportJob({id:'i',projectId:'p',sourceFingerprint:'file',parserVersion:1,source:{kind:'native-ndjson',fileId:'file',targetCanvasId:'canvas',byteLength:10}});assert.equal(job.version,3);assert.equal(job.source.integrityVersion,1);validateImportJob(job); assert.throws(()=>validateImportJob({...job,cursor:1}),/import_source_integrity_missing/);assert.throws(()=>validateImportJob({...job,stableIds:{'element:A':'new-A'}}),/import_source_integrity_missing/);assert.throws(()=>validateImportJob({...job,source:{...job.source,sha256:'invalid'}}),/invalid_import_source/);assert.throws(()=>validateImportJob({...job,source:{...job.source,integrityVersion:3}}),/import_source_integrity_interpreter_unavailable/); const source={...job.source};delete source.integrityVersion;validateImportJob({...job,version:2,source,cursor:1});validateImportJob({...job,source:{...job.source,sha256:'a'.repeat(64)},cursor:1}); });