import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import test from "node:test"; import ts from "typescript"; const repoRoot = path.resolve(import.meta.dirname, "../../.."); const sourcePath = path.join(repoRoot, "web/protocol/file-byte-reader.ts"); const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true, }); assert.deepEqual(transpiled.diagnostics, []); const moduleUrl = `data:text/javascript;base64,${Buffer.from(transpiled.outputText).toString("base64")}`; const { FileByteReadError, readFileBytes } = await import(moduleUrl); function chunkSource(chunks, declaredSize = chunks.reduce((total, chunk) => total + (chunk.byteLength ?? chunk.length), 0)) { return { size: declaredSize, stream() { let index = 0; return new ReadableStream({ pull(controller) { if (index === chunks.length) controller.close(); else controller.enqueue(Uint8Array.from(chunks[index++])); }, }); }, }; } test("M7-03 reports progress from exact consumed byte counts", async () => { const observations = []; const result = await readFileBytes(chunkSource([[1, 2], [3, 4, 5], [6]]), { signal: new AbortController().signal, onProgress: (item) => observations.push(item), }); assert.deepEqual(Array.from(new Uint8Array(result)), [1, 2, 3, 4, 5, 6]); assert.deepEqual(observations.map(({ phase, bytesRead, totalBytes, fraction }) => [phase, bytesRead, totalBytes, fraction]), [ ["STARTED", 0, 6, 0], ["READING", 2, 6, 2 / 6], ["READING", 5, 6, 5 / 6], ["READING", 6, 6, 1], ["COMPLETED", 6, 6, 1], ]); }); test("M7-03 cancels between chunks without reporting completion", async () => { const controller = new AbortController(); const observations = []; const resources = []; await assert.rejects( readFileBytes(chunkSource([[1, 2], [3, 4], [5, 6]]), { signal: controller.signal, onProgress: (item) => { observations.push(item); if (item.phase === "READING" && item.bytesRead === 2) controller.abort(); }, onResourceState: (state) => resources.push(state), }), (error) => error instanceof FileByteReadError && error.code === "FILE_READ_CANCELLED", ); assert.deepEqual(observations.map((item) => [item.phase, item.bytesRead]), [ ["STARTED", 0], ["READING", 2], ["CANCELLED", 2], ]); assert.deepEqual(resources, [ { liveReaders: 1, liveInputBytes: 6, liveStagingFiles: 0 }, { liveReaders: 0, liveInputBytes: 0, liveStagingFiles: 0 }, ]); }); test("M7-03 rejects streams shorter or longer than their declared byte size", async () => { await assert.rejects( readFileBytes(chunkSource([[1, 2]], 3), { signal: new AbortController().signal }), (error) => error instanceof FileByteReadError && error.code === "FILE_READ_SIZE_MISMATCH", ); await assert.rejects( readFileBytes(chunkSource([[1, 2, 3]], 2), { signal: new AbortController().signal }), (error) => error instanceof FileByteReadError && error.code === "FILE_READ_SIZE_MISMATCH", ); });