import { describe, expect, it, vi } from "vitest"; import { createModelPrimitive, createValidModelDocument, } from "../canonical/model-document-test-support"; import type { ToolcraftModelFormatAdapter } from "../model-import-types"; import { TOOLCRAFT_MODEL_IMPORT_LIMIT_CEILINGS } from "../model-import-limits"; import { digestModelSourceBytes } from "../model-source-digest"; import { analyzeToolcraftModel } from "../topology/model-topology-analysis"; import { createToolcraftModelAnalysisCacheKey, createToolcraftModelDecodeCacheKey, createToolcraftModelRepairCacheKey, TOOLCRAFT_MODEL_WORKER_MAX_DIAGNOSTICS, type ToolcraftModelWorkerRequest, type ToolcraftModelWorkerResponse, } from "./model-import-worker-protocol"; import { createToolcraftBoundedWorkerCache, createToolcraftWorkerCacheCoordinator, } from "./bounded-worker-cache"; import { createToolcraftModelWorkerRuntime } from "./model-import-worker-runtime"; import { decodeRequest, limits, withClaimedAggregateDigest, } from "./model-import-worker-runtime-test-support"; describe("model import worker cache keys", () => { it("uses exactly the stage-owned deterministic key components", () => { const decode = createToolcraftModelDecodeCacheKey({ adapterVersion: "adapter@1", format: "glb", geometryDecoderVersion: "decoder@1", verifiedSourceTransferDigest: "sha256:source", }); expect(decode).toEqual(createToolcraftModelDecodeCacheKey({ adapterVersion: "adapter@1", format: "glb", geometryDecoderVersion: "decoder@1", verifiedSourceTransferDigest: "sha256:source", })); expect(decode).not.toEqual(createToolcraftModelDecodeCacheKey({ adapterVersion: "adapter@1", format: "gltf", geometryDecoderVersion: "decoder@1", verifiedSourceTransferDigest: "sha256:source", })); expect(createToolcraftModelAnalysisCacheKey({ canonicalDocumentDigest: "sha256:document", topologyProfile: "realtime-mesh", })).not.toEqual(createToolcraftModelAnalysisCacheKey({ canonicalDocumentDigest: "sha256:document", topologyProfile: "solid-mesh", })); expect(createToolcraftModelRepairCacheKey({ canonicalDocumentDigest: "sha256:document", repairPlanDigest: "sha256:plan-a", })).not.toEqual(createToolcraftModelRepairCacheKey({ canonicalDocumentDigest: "sha256:document", repairPlanDigest: "sha256:plan-b", })); }); }); describe("bounded worker cache configuration", () => { it.each([ ["infinite entries", Infinity], ["NaN entries", Number.NaN], ["zero entries", 0], ["negative entries", -1], ] as const)("rejects %s defensively", (_label, config) => { expect(() => createToolcraftBoundedWorkerCache({ coordinator: createToolcraftWorkerCacheCoordinator({ maxAggregateBytes: 1 }), maxEntries: config, sizeOf: () => 1, })).toThrow(/finite positive safe integer/u); }); }); describe("createToolcraftModelWorkerRuntime", () => { it("publishes monotonic phase progress below completion, diagnostics, and one transferred result", async () => { const document = createValidModelDocument(); const adapter: ToolcraftModelFormatAdapter = { adapterVersion: "test-adapter@1", decode: vi.fn(async () => ({ appearanceResources: [], diagnostics: [{ affectedCount: 1, code: "decoder-note", explanation: "Decoder note.", severity: "info" as const, }], document, })), format: "glb", rootExtensions: [".glb"], workerCapable: true, }; const posted: Array<{ response: ToolcraftModelWorkerResponse; transfer: readonly Transferable[]; }> = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [adapter], post: (response, transfer = []) => posted.push({ response, transfer }), }); await runtime.handleMessage(decodeRequest(1)); const progress = posted .map(({ response }) => response) .filter((response) => response.kind === "progress"); for (const phase of ["decoding", "analyzing"] as const) { const values = progress .filter((entry) => entry.phase === phase) .map(({ progress: value }) => value); expect(values.length).toBeGreaterThan(0); expect(values).toEqual([...values].sort((left, right) => left - right)); expect(Math.max(...values)).toBeLessThan(1); } expect(posted.some(({ response }) => response.kind === "diagnostic")).toBe(true); const terminal = posted.at(-1)!; expect(terminal.response.kind).toBe("result"); expect(terminal.transfer).toHaveLength(1); if (terminal.response.kind !== "result") throw new Error("Expected result."); expect(terminal.transfer[0]).toBe(terminal.response.result.canonicalDocument); expect(new Set(terminal.transfer).size).toBe(terminal.transfer.length); }); it("uses one immutable bounded diagnostic snapshot for stream, result, and cache", async () => { const document = createValidModelDocument({ primitives: [createModelPrimitive({ normals: undefined })], }); const baseAnalysis = analyzeToolcraftModel(document, "realtime-mesh", limits); expect(baseAnalysis.outcome).toBe("repairable"); expect(baseAnalysis.repairPlan).toBeDefined(); const sourceDiagnostic = baseAnalysis.diagnostics.find( ({ code }) => code === "normals-missing", )!; const analysisDiagnosticCount = TOOLCRAFT_MODEL_WORKER_MAX_DIAGNOSTICS + 37; const decoderDiagnosticCount = TOOLCRAFT_MODEL_WORKER_MAX_DIAGNOSTICS + 41; const oversizedAnalysis = Object.freeze({ ...baseAnalysis, diagnostics: Object.freeze(Array.from( { length: analysisDiagnosticCount }, (_, index) => Object.freeze({ ...sourceDiagnostic, primitiveId: `analysis-primitive-${index}`, }), )), }); const decode = vi.fn(async () => ({ appearanceResources: [], diagnostics: Array.from({ length: decoderDiagnosticCount }, (_, index) => ({ affectedCount: 1, code: "decoder-note", explanation: `Bounded decoder diagnostic ${index}.`, severity: "info" as const, })), document, })); const analyze = vi.fn(() => oversizedAnalysis); const responses: ToolcraftModelWorkerResponse[] = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [{ adapterVersion: "test-adapter@1", decode, format: "glb", rootExtensions: [".glb"], workerCapable: true, }], analyze, post: (response) => responses.push(response), }); await runtime.handleMessage(decodeRequest(1)); await runtime.handleMessage(decodeRequest(2)); expect(decode).toHaveBeenCalledTimes(1); expect(analyze).toHaveBeenCalledTimes(1); for (const jobId of ["decode-1", "decode-2"]) { const streamed = responses.filter( (response) => response.jobId === jobId && response.kind === "diagnostic", ); expect(streamed).toHaveLength(TOOLCRAFT_MODEL_WORKER_MAX_DIAGNOSTICS); expect(streamed.at(-1)).toMatchObject({ diagnostic: { affectedCount: decoderDiagnosticCount + analysisDiagnosticCount - (TOOLCRAFT_MODEL_WORKER_MAX_DIAGNOSTICS - 1), code: "diagnostics-truncated", severity: "repairable", }, }); } const results = responses.filter((response) => response.kind === "result"); expect(results).toHaveLength(2); const [first, second] = results; if (first?.kind !== "result" || second?.kind !== "result") { throw new Error("Expected bounded worker results."); } expect(first.result.analysis).toEqual(second.result.analysis); expect(Object.isFrozen(first.result.analysis)).toBe(true); expect(Object.isFrozen(first.result.analysis.diagnostics)).toBe(true); expect(first.result.analysis.diagnostics).toHaveLength( TOOLCRAFT_MODEL_WORKER_MAX_DIAGNOSTICS, ); expect(first.result.analysis.diagnostics.at(-1)).toMatchObject({ affectedCount: analysisDiagnosticCount - (TOOLCRAFT_MODEL_WORKER_MAX_DIAGNOSTICS - 1), code: "diagnostics-truncated", severity: "repairable", }); expect(first.result.analysis.outcome).toBe(baseAnalysis.outcome); expect("repairPlan" in first.result.analysis).toBe(false); expect(first.result.operation).toBe("decode-and-analyze"); if (first.result.operation !== "decode-and-analyze") { throw new Error("Expected decode result."); } expect(first.result.repairPlanEnvelope?.planDigest).toBe( baseAnalysis.repairPlan?.planDigest, ); expect(runtime.inspectCacheSizes()).toMatchObject({ analysis: 1, decode: 1, repair: 0, }); }); it("reuses bounded decode and analysis caches by their exact keys", async () => { const document = createValidModelDocument(); const decode = vi.fn(async () => ({ appearanceResources: [], diagnostics: [], document, })); const analyze = vi.fn(analyzeToolcraftModel); const responses: ToolcraftModelWorkerResponse[] = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [{ adapterVersion: "test-adapter@1", decode, format: "glb", rootExtensions: [".glb"], workerCapable: true, }], analyze, cacheLimits: { maxAggregateBytes: 1024 * 1024, maxEntriesPerStage: 2 }, post: (response, transfer = []) => { responses.push(transfer.length === 0 ? response : structuredClone(response, { transfer: [...transfer] })); }, }); await runtime.handleMessage(decodeRequest(1, "realtime-mesh")); await runtime.handleMessage(decodeRequest(2, "solid-mesh")); await runtime.handleMessage(decodeRequest(3, "solid-mesh")); expect(decode).toHaveBeenCalledTimes(1); expect(analyze).toHaveBeenCalledTimes(2); const resultBuffers = responses .filter((response) => response.kind === "result") .map((response) => response.result.canonicalDocument); expect(resultBuffers).toHaveLength(3); expect(new Set(resultBuffers).size).toBe(3); for (const buffer of resultBuffers) expect(buffer.byteLength).toBeGreaterThan(0); const cacheState = runtime.inspectCacheSizes(); expect(cacheState).toMatchObject({ analysis: 2, decode: 1, repair: 0, }); expect(cacheState.retainedBytes).toBeLessThanOrEqual( cacheState.maxAggregateBytes, ); runtime.clearCaches(); expect(runtime.inspectCacheSizes()).toMatchObject({ analysis: 0, decode: 0, repair: 0, retainedBytes: 0, }); runtime.dispose(); runtime.dispose(); }); it("keys decode by verified source transfer rather than the claimed aggregate digest", async () => { const decode = vi.fn(async () => ({ appearanceResources: [], diagnostics: [], document: createValidModelDocument(), })); const runtime = createToolcraftModelWorkerRuntime({ adapters: [{ adapterVersion: "test-adapter@1", decode, format: "glb", rootExtensions: [".glb"], workerCapable: true, }], post: vi.fn(), }); const first = withClaimedAggregateDigest( decodeRequest(1, "realtime-mesh", 1), `sha256:${"1".repeat(64)}`, ); const changedClaim = withClaimedAggregateDigest( decodeRequest(2, "realtime-mesh", 1), `sha256:${"2".repeat(64)}`, ); const changedTransfer = withClaimedAggregateDigest( decodeRequest(3, "realtime-mesh", 9), `sha256:${"2".repeat(64)}`, ); if (first.kind !== "decode-and-analyze" || changedClaim.kind !== "decode-and-analyze" || changedTransfer.kind !== "decode-and-analyze") { throw new Error("Expected decode requests."); } expect(changedClaim.payload.bundle.aggregateDigest).not.toBe( first.payload.bundle.aggregateDigest, ); expect(changedClaim.payload.bundle.sourceFiles[0]!.contentDigest).toBe( first.payload.bundle.sourceFiles[0]!.contentDigest, ); expect(changedTransfer.payload.bundle.aggregateDigest).toBe( changedClaim.payload.bundle.aggregateDigest, ); expect(changedTransfer.payload.bundle.sourceFiles[0]!.contentDigest).not.toBe( changedClaim.payload.bundle.sourceFiles[0]!.contentDigest, ); await runtime.handleMessage(first); await runtime.handleMessage(changedClaim); await runtime.handleMessage(changedTransfer); expect(decode).toHaveBeenCalledTimes(2); expect(runtime.inspectCacheSizes().decode).toBe(2); }); it("revalidates cached analysis under a later normalized limit snapshot", async () => { const decode = vi.fn(async () => ({ appearanceResources: [], diagnostics: [], document: createValidModelDocument(), })); const analyze = vi.fn(analyzeToolcraftModel); const responses: ToolcraftModelWorkerResponse[] = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [{ adapterVersion: "test-adapter@1", decode, format: "glb", rootExtensions: [".glb"], workerCapable: true, }], analyze, post: (response) => responses.push(response), }); const first = decodeRequest(1); const secondBase = decodeRequest(2); if (secondBase.kind !== "decode-and-analyze") throw new Error("Expected decode request."); const second: ToolcraftModelWorkerRequest = { ...secondBase, payload: { ...secondBase.payload, limits: { ...limits, maxSourceBytes: 1_024 }, }, }; await runtime.handleMessage(first); await runtime.handleMessage(second); expect(decode).toHaveBeenCalledTimes(1); expect(analyze).toHaveBeenCalledTimes(2); const result = responses.filter((response) => response.kind === "result").at(-1); expect(result).toMatchObject({ kind: "result", result: { analysis: { limits: { maxSourceBytes: 1_024 } } }, }); }); it("rejects a mismatched per-file digest before decoder invocation", async () => { const decode = vi.fn(async () => ({ appearanceResources: [], diagnostics: [], document: createValidModelDocument(), })); const responses: ToolcraftModelWorkerResponse[] = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [{ adapterVersion: "test-adapter@1", decode, format: "glb", rootExtensions: [".glb"], workerCapable: true, }], post: (response) => responses.push(response), }); const base = decodeRequest(1); if (base.kind !== "decode-and-analyze") throw new Error("Expected decode request."); const request: ToolcraftModelWorkerRequest = { ...base, payload: { ...base.payload, bundle: { ...base.payload.bundle, sourceFiles: base.payload.bundle.sourceFiles.map((file) => ({ ...file, contentDigest: `sha256:${"f".repeat(64)}`, })), }, }, }; await runtime.handleMessage(request); expect(decode).not.toHaveBeenCalled(); expect(responses.at(-1)).toMatchObject({ kind: "error", feedback: { category: "bundle", code: "source-file-digest-mismatch" }, }); }); it("rejects excessive transfer metadata before decoder invocation", async () => { const decode = vi.fn(async () => ({ appearanceResources: [], diagnostics: [], document: createValidModelDocument(), })); const responses: ToolcraftModelWorkerResponse[] = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [{ adapterVersion: "test-adapter@1", decode, format: "glb", rootExtensions: [".glb"], workerCapable: true, }], post: (response) => responses.push(response), }); const base = decodeRequest(1); if (base.kind !== "decode-and-analyze") throw new Error("Expected decode request."); const request: ToolcraftModelWorkerRequest = { ...base, payload: { ...base.payload, bundle: { ...base.payload.bundle, rootPath: "x".repeat(1_048_577), }, }, }; await runtime.handleMessage(request); expect(decode).not.toHaveBeenCalled(); expect(responses.at(-1)).toMatchObject({ kind: "error", feedback: { category: "bundle", code: "source-transfer-metadata-limit-exceeded", }, }); }); });