import { strToU8, zipSync } from "fflate"; import { describe, expect, it } from "vitest"; import type { ToolcraftModelWorkerResponse } from "./model-import-worker-protocol"; import { createToolcraftModelWorkerRuntime } from "./model-import-worker-runtime"; import { digestToolcraftModelWorkerBytes } from "./model-import-worker-receipt"; import { limits } from "./model-import-worker-runtime-test-support"; describe("model package worker extraction", () => { it("publishes monotonic extracting progress and transfers each owned entry once", async () => { const archive = zipSync({ "robot/mesh.bin": new Uint8Array([1, 2, 3, 4]), "robot/scene.gltf": strToU8('{"asset":{"version":"2.0"}}'), }); const archiveBuffer = archive.slice().buffer; const posted: Array<{ response: ToolcraftModelWorkerResponse; transfer: readonly Transferable[]; }> = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [], post: (response, transfer = []) => posted.push({ response, transfer }), }); await runtime.handleMessage({ generation: 1, jobId: "extract-1", kind: "extract-model-package", payload: { archive: archiveBuffer, archiveDigest: digestToolcraftModelWorkerBytes(archiveBuffer), limits, }, }); const progress = posted .map(({ response }) => response) .flatMap((response) => response.kind === "progress" && response.phase === "extracting" ? [response.progress] : [] ); expect(progress.length).toBeGreaterThan(0); expect(progress).toEqual([...progress].sort((left, right) => left - right)); expect(progress.at(-1)).toBeLessThan(1); const terminal = posted.at(-1)!; expect(terminal.response.kind).toBe("package-result"); if (terminal.response.kind !== "package-result") { throw new Error("Expected a package extraction result."); } expect(terminal.response.result.entries.map(({ path }) => path)).toEqual([ "robot/mesh.bin", "robot/scene.gltf", ]); expect(terminal.transfer).toEqual( terminal.response.result.entries.map(({ bytes }) => bytes), ); expect(new Set(terminal.transfer).size).toBe(terminal.transfer.length); }); it("rejects an archive whose transferred bytes do not match its digest", async () => { const archive = zipSync({ "scene.gltf": strToU8("{}") }); const responses: ToolcraftModelWorkerResponse[] = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [], post: (response) => responses.push(response), }); await runtime.handleMessage({ generation: 1, jobId: "extract-forged", kind: "extract-model-package", payload: { archive: archive.slice().buffer, archiveDigest: `sha256:${"0".repeat(64)}`, limits, }, }); expect(responses.at(-1)).toMatchObject({ feedback: { category: "bundle", code: "archive-digest-mismatch" }, kind: "error", }); }); });