import { describe, expect, it, vi } from "vitest"; import { createToolcraftModelAppearanceResourceRef } from "../canonical/model-appearance-resource"; import { createModelTexture, createValidModelDocumentV2, } from "../canonical/model-document-test-support"; import type { ToolcraftModelWorkerResponse } from "./model-import-worker-protocol"; import { digestToolcraftModelWorkerBytes } from "./model-import-worker-receipt"; import { createToolcraftModelWorkerRuntime } from "./model-import-worker-runtime"; import { decodeRequest } from "./model-import-worker-runtime-test-support"; describe("model import worker appearance resources", () => { it("owns, caches, receipts, and independently transfers v2 appearance bytes", async () => { const textureBytes = new Uint8Array([137, 80, 78, 71]); const contentDigest = digestToolcraftModelWorkerBytes(textureBytes); const resourceRef = createToolcraftModelAppearanceResourceRef(contentDigest); const document = createValidModelDocumentV2({ textures: [createModelTexture({ contentDigest, resourceRef })], }); const decode = vi.fn(async () => ({ appearanceResources: [{ bytes: textureBytes.buffer, contentDigest, mimeType: "image/png" as const, resourceRef, }], diagnostics: [], document, })); const posted: Array<{ response: ToolcraftModelWorkerResponse; transfer: readonly Transferable[]; }> = []; const runtime = createToolcraftModelWorkerRuntime({ adapters: [{ adapterVersion: "test-adapter@1", decode, format: "glb", rootExtensions: [".glb"], workerCapable: true, }], post: (response, transfer = []) => posted.push({ response, transfer }), }); await runtime.handleMessage(decodeRequest(1)); new Uint8Array(textureBytes.buffer)[0] = 0; await runtime.handleMessage(decodeRequest(2)); expect(decode).toHaveBeenCalledTimes(1); const drafts = posted.filter(({ response }) => response.kind === "draft"); const results = posted.filter(({ response }) => response.kind === "result"); expect(drafts).toHaveLength(2); expect(results).toHaveLength(2); for (const published of [...drafts, ...results]) { expect(published.transfer).toHaveLength(2); expect(new Set(published.transfer).size).toBe(2); const resource = published.response.kind === "draft" ? published.response.draft.appearanceResources[0] : published.response.kind === "result" && published.response.result.operation === "decode-and-analyze" ? published.response.result.appearanceResources[0] : undefined; expect(resource).toMatchObject({ contentDigest, resourceRef }); expect(new Uint8Array(resource!.bytes)[0]).toBe(137); expect(published.transfer).toContain(resource!.bytes); } const publishedBuffers = [...drafts, ...results].map((published) => { const response = published.response; if (response.kind === "draft") { return response.draft.appearanceResources[0]!.bytes; } if (response.kind === "result" && response.result.operation === "decode-and-analyze") { return response.result.appearanceResources[0]!.bytes; } throw new Error("Expected decode appearance resource."); }); expect(new Set(publishedBuffers).size).toBe(publishedBuffers.length); }); });