import { describe, expect, it } from "vitest"; import { createToolcraftModelFormatAdapterRegistry } from "./formats/model-format-adapter"; import { getToolcraftModelPackageMimeType } from "./model-package-path"; import type { ToolcraftExtractedModelPackageEntry } from "./model-package-types"; import { createToolcraftModelSourceBundleSnapshotFromExtractedPackage, } from "./model-source-bundle"; import { createModelSourceTestBundle as bundle, modelSourceTestAdapter as adapter, modelSourceTestBinaryFile as binaryFile, modelSourceTestGltfWithBuffers as gltfWithBuffers, modelSourceTestTextFile as textFile, } from "./model-source-bundle-test-support"; import { createModelSourceResourceRef, digestModelSourceBytes, } from "./model-source-digest"; describe("model source package normalization", () => { it("canonicalizes equivalent selected-folder and ZIP packages identically", async () => { const rootText = gltfWithBuffers(["buffers/mesh.bin"]); const rootBytes = new TextEncoder().encode(rootText); const meshBytes = new Uint8Array([1, 2, 3]); const textureBytes = new Uint8Array([4, 5, 6]); const selected = await bundle([ textFile("scene.gltf", rootText, { path: "package/scene.gltf", type: "model/gltf+json", }), binaryFile("mesh.bin", meshBytes, { path: "package/buffers/mesh.bin", }), binaryFile("albedo.png", textureBytes, { path: "package/textures/albedo.png", type: "image/png", }), ]); const extracted = [ ["package/textures/albedo.png", textureBytes], ["package/buffers/mesh.bin", meshBytes], ["package/scene.gltf", rootBytes], ].map(([path, bytes]) => { const owned = bytes as Uint8Array; return Object.freeze({ bytes: owned.slice().buffer, compressedBytes: owned.byteLength, contentDigest: digestModelSourceBytes(owned), mimeType: getToolcraftModelPackageMimeType(path as string), path: path as string, uncompressedBytes: owned.byteLength, }) satisfies ToolcraftExtractedModelPackageEntry; }); const archiveDigest = `sha256:${"f".repeat(64)}`; const zipped = await createToolcraftModelSourceBundleSnapshotFromExtractedPackage( extracted, { archive: { byteLength: 99, contentDigest: archiveDigest, displayName: "package.zip", mimeType: "application/zip", resourceRef: createModelSourceResourceRef( archiveDigest, "application/zip", ), }, kind: "zip", }, { registry: createToolcraftModelFormatAdapterRegistry([ adapter("gltf", ".gltf"), ]) }, ); expect(zipped.bundle.rootPath).toBe(selected.rootPath); expect(zipped.bundle.sourceFiles).toEqual(selected.sourceFiles); expect(zipped.bundle.aggregateDigest).toBe(selected.aggregateDigest); expect(selected.packageSource).toEqual({ kind: "selected-files" }); expect(zipped.bundle.packageSource).toMatchObject({ kind: "zip" }); }); it("selects the first normalized root and summarizes ignored roots", async () => { const result = await bundle([ binaryFile("z.glb", [2], { path: "package/z.glb" }), binaryFile("a.glb", [1], { path: "package/a.glb" }), binaryFile("preview.png", [3], { path: "package/preview.png", type: "image/png", }), ]); expect(result.rootPath).toBe("package/a.glb"); expect(result.sourceFiles.map(({ path }) => path)).toEqual([ "package/a.glb", "package/preview.png", ]); expect(result.diagnostics).toEqual([ expect.objectContaining({ affectedCount: 1, code: "ignored-model-root", severity: "info", }), ]); }); });