import { describe, expect, it, vi } from "vitest"; import { encodeToolcraftModelDocument } from "../canonical/model-document-codec"; import { digestToolcraftModelDocument } from "../canonical/model-document-digest"; import { createModelPrimitive, createValidModelDocument } from "../canonical/model-document-test-support"; import type { ToolcraftModelFormatAdapter } from "../model-import-types"; import { digestToolcraftModelWorkerBytes } from "./model-import-worker-receipt"; import type { ToolcraftModelDecodeAnalyzeWorkerResult, ToolcraftModelWorkerResponse, } from "./model-import-worker-protocol"; import { createToolcraftModelWorkerRuntime } from "./model-import-worker-runtime"; import { decodeRequest, limits } from "./model-import-worker-runtime-test-support"; function decodeResult( responses: readonly ToolcraftModelWorkerResponse[], ): ToolcraftModelDecodeAnalyzeWorkerResult { const response = [...responses].reverse().find(({ kind }) => kind === "result"); if (response?.kind !== "result" || response.result.operation !== "decode-and-analyze") { throw new Error("Expected decode result."); } return response.result; } describe("model worker opaque repair-plan envelope", () => { it("round-trips analysis envelope bytes back through worker repair", async () => { const document = createValidModelDocument({ primitives: [createModelPrimitive({ normals: undefined })], }); const adapter: ToolcraftModelFormatAdapter = { adapterVersion: "test-adapter@1", decode: vi.fn(async () => ({ appearanceResources: [], diagnostics: [], document, })), format: "glb", rootExtensions: [".glb"], workerCapable: true, }; const responses: ToolcraftModelWorkerResponse[] = []; const transfers: Array = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [adapter], post: (response, transfer = []) => { responses.push(response); transfers.push(transfer); }, }); await runtime.handleMessage(decodeRequest(1)); const decoded = decodeResult(responses); expect(decoded.analysis.outcome).toBe("repairable"); expect("repairPlan" in decoded.analysis).toBe(false); expect(decoded.repairPlanEnvelope?.bytes).toBeInstanceOf(ArrayBuffer); const decodeResponseIndex = responses.findIndex( (response) => response.kind === "result" && response.result.operation === "decode-and-analyze", ); expect(transfers[decodeResponseIndex]).toEqual([ decoded.canonicalDocument, decoded.repairPlanEnvelope?.bytes, ]); await runtime.handleMessage({ generation: 2, jobId: "repair-round-trip", kind: "repair", payload: { canonicalDocument: decoded.canonicalDocument, canonicalDocumentDigest: decoded.canonicalDocumentDigest, limits, repairPlanEnvelope: decoded.repairPlanEnvelope!, }, }); const repaired = [...responses].reverse().find( (response) => response.kind === "result" && response.result.operation === "repair", ); expect(repaired).toMatchObject({ kind: "result", result: { analysis: { outcome: "clean" }, operation: "repair", repairPlanDigest: decoded.repairPlanEnvelope?.planDigest, receiptDigest: expect.stringMatching(/^sha256:[0-9a-f]{64}$/u), }, }); }); it("rejects a malformed envelope even when its byte digest is recomputed", async () => { const repair = vi.fn(() => createValidModelDocument()); const responses: ToolcraftModelWorkerResponse[] = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [], post: (response) => responses.push(response), repair, }); const malformedBytes = new TextEncoder().encode("not-an-envelope").buffer; const document = createValidModelDocument(); const canonicalDocument = encodeToolcraftModelDocument(document).slice().buffer; await runtime.handleMessage({ generation: 1, jobId: "malformed-envelope", kind: "repair", payload: { canonicalDocument, canonicalDocumentDigest: digestToolcraftModelDocument(document), limits, repairPlanEnvelope: { bytes: malformedBytes, envelopeDigest: digestToolcraftModelWorkerBytes(malformedBytes), planDigest: `sha256:${"1".repeat(64)}`, version: 1, }, }, }); expect(repair).not.toHaveBeenCalled(); expect(responses.at(-1)).toMatchObject({ feedback: { category: "repair" }, kind: "error", }); }); });