import { describe, expect, it, vi } from "vitest"; import type { ToolcraftModelFormat } from "../../schema/types"; import type { ToolcraftModelFormatAdapter } from "../model-import-types"; import { createToolcraftModelFormatAdapterRegistry, getToolcraftAdvertisedModelFormats, getToolcraftModelRootExtensions, selectToolcraftModelFormatAdapter, } from "./model-format-adapter"; function adapter( format: ToolcraftModelFormat, rootExtensions: readonly string[], ): ToolcraftModelFormatAdapter { return { adapterVersion: `${format}-1`, decode: vi.fn(async () => { throw new Error("decode is not used by registry tests"); }), format, rootExtensions, workerCapable: true, }; } describe("model format adapter registry", () => { it("selects deterministically by one root extension", () => { const gltf = adapter("gltf", [".gltf"]); const glb = adapter("glb", [".glb"]); const registry = createToolcraftModelFormatAdapterRegistry([gltf, glb]); expect(selectToolcraftModelFormatAdapter(registry, ".GLB")).toMatchObject({ adapterVersion: "glb-1", format: "glb", rootExtensions: [".glb"], workerCapable: true, }); expect(selectToolcraftModelFormatAdapter(registry, ".obj")).toBeUndefined(); expect( selectToolcraftModelFormatAdapter(registry, ".glb.obj"), ).toBeUndefined(); }); it("derives advertised formats and root extensions only from registrations", () => { const emptyRegistry = createToolcraftModelFormatAdapterRegistry([]); const registry = createToolcraftModelFormatAdapterRegistry([ adapter("gltf", [".gltf"]), adapter("glb", [".glb"]), ]); expect(getToolcraftAdvertisedModelFormats(emptyRegistry)).toEqual([]); expect(getToolcraftModelRootExtensions(emptyRegistry)).toEqual([]); expect(getToolcraftAdvertisedModelFormats(registry)).toEqual([ "glb", "gltf", ]); expect(getToolcraftModelRootExtensions(registry)).toEqual([ ".glb", ".gltf", ]); }); it("snapshots and freezes registration inputs", () => { const mutableExtensions = [".gltf"]; const mutableAdapter = adapter("gltf", mutableExtensions); const originalDecode = mutableAdapter.decode; const registrations = [mutableAdapter]; const registry = createToolcraftModelFormatAdapterRegistry(registrations); mutableExtensions[0] = ".changed"; registrations.length = 0; mutableAdapter.adapterVersion = "changed"; mutableAdapter.decode = vi.fn(); const selected = selectToolcraftModelFormatAdapter(registry, ".gltf"); expect(selected).toMatchObject({ adapterVersion: "gltf-1", rootExtensions: [".gltf"], }); expect(selected?.decode).toBe(originalDecode); expect(Object.isFrozen(registry)).toBe(true); expect(Object.isFrozen(registry.adapters)).toBe(true); expect(Object.isFrozen(selected)).toBe(true); expect(Object.isFrozen(selected?.rootExtensions)).toBe(true); }); it("reads each adapter metadata field once while snapshotting", () => { const volatile = adapter("glb", [".glb"]); let extensionReads = 0; Object.defineProperty(volatile, "rootExtensions", { configurable: true, get: () => { extensionReads += 1; return extensionReads === 1 ? [".glb"] : [".obj"]; }, }); const registry = createToolcraftModelFormatAdapterRegistry([volatile]); expect(extensionReads).toBe(1); expect(selectToolcraftModelFormatAdapter(registry, ".glb")).toBeDefined(); expect(selectToolcraftModelFormatAdapter(registry, ".obj")).toBeUndefined(); }); it("rejects duplicate format and root-extension ownership", () => { expect(() => createToolcraftModelFormatAdapterRegistry([ adapter("gltf", [".gltf"]), adapter("gltf", [".json-gltf"]), ]), ).toThrow(/Duplicate adapter format "gltf"/u); expect(() => createToolcraftModelFormatAdapterRegistry([ adapter("gltf", [".model"]), adapter("glb", [".model"]), ]), ).toThrow(/Duplicate root extension "\.model"/u); }); it("rejects sparse registration arrays", () => { const registrations = [ adapter("glb", [".glb"]), adapter("gltf", [".gltf"]), ] as ToolcraftModelFormatAdapter[] | undefined[]; delete registrations[0]; expect(() => createToolcraftModelFormatAdapterRegistry( registrations as ToolcraftModelFormatAdapter[], ), ).toThrow(/sparse entry at index 0/u); }); it.each([ ["missing decode", { ...adapter("glb", [".glb"]), decode: undefined }], [ "wrong worker capability", { ...adapter("glb", [".glb"]), workerCapable: false }, ], ["empty version", { ...adapter("glb", [".glb"]), adapterVersion: " " }], ["unknown format", { ...adapter("glb", [".glb"]), format: "usd" }], ["empty extensions", { ...adapter("glb", [".glb"]), rootExtensions: [] }], ["missing dot", { ...adapter("glb", [".glb"]), rootExtensions: ["glb"] }], [ "uppercase extension", { ...adapter("glb", [".glb"]), rootExtensions: [".GLB"] }, ], [ "path extension", { ...adapter("glb", [".glb"]), rootExtensions: ["./glb"] }, ], [ "duplicate own extension", { ...adapter("glb", [".glb"]), rootExtensions: [".glb", ".glb"] }, ], ])("rejects invalid adapter metadata: %s", (_label, invalidAdapter) => { expect(() => createToolcraftModelFormatAdapterRegistry([ invalidAdapter as ToolcraftModelFormatAdapter, ]), ).toThrow(/model-format-adapter-registry/u); }); });