import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import test from "node:test"; import ts from "typescript"; const root = path.resolve(import.meta.dirname, "../../.."); const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "library-linked-missing-unit-")); const sourcePath = path.join(root, "web/protocol/library-linked-missing.ts"); const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true, }); assert.deepEqual(transpiled.diagnostics, []); fs.writeFileSync(path.join(temporary, "library-linked-missing.mjs"), transpiled.outputText); const missing = await import(pathToFileURL(path.join(temporary, "library-linked-missing.mjs"))); const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-03I/manifest.json"), "utf8")); const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex"); const library = (value) => `library:${String(value).repeat(64).slice(0, 64)}`; const digest = (value) => crypto.createHash("sha256").update(String(value)).digest("hex"); const reference = (sourceLibraryId, generation, revision, marker, status = "AVAILABLE") => ({ sourceLibraryId, sourceLocator: `project://libraries/${marker}.blend`, sourceSha256: digest(`${marker}-source`), sourceGeneration: generation, sourceRevision: revision, dataBlockIds: [`Object/${marker}`, `Mesh/${marker}`], status, placeholder: status === "MISSING" ? { kind: "MISSING_LIBRARY", sourceLibraryId, dataBlockIds: [`Object/${marker}`, `Mesh/${marker}`], } : null, }); test("M12-03I binds the missing-library protocol and evidence artifacts", () => { assert.equal(manifest.task, "M12-03I"); assert.equal(manifest.parentTask, "M12-03H"); assert.equal(manifest.nextTask, "M12-03J"); for (const artifact of Object.values(manifest.artifacts)) assert.equal(sha256(artifact.path), artifact.sha256, artifact.path); }); test("M12-03I marks only the matching reference missing and preserves its original source", () => { const sourceLibraryId = library("a"); const otherLibraryId = library("b"); const current = reference(sourceLibraryId, 4, 9, "primary"); const other = reference(otherLibraryId, 1, 2, "other"); const state = { schemaVersion: 1, references: [current, other] }; const decision = missing.markLinkedLibraryMissing(state, { schemaVersion: 1, operation: "MARK_MISSING", sourceLibraryId, sourceLocator: current.sourceLocator, sourceSha256: current.sourceSha256, expectedGeneration: 4, expectedRevision: 9, }); assert.equal(decision.status, "MARKED"); assert.equal(decision.code, null); assert.deepEqual(decision.state.references[0], { ...current, status: "MISSING", placeholder: { kind: "MISSING_LIBRARY", sourceLibraryId, dataBlockIds: current.dataBlockIds, } }); assert.deepEqual(decision.state.references[1], other); assert.deepEqual(state.references, [current, other]); }); test("M12-03I preserves the reference on stale generation and source hash drift", () => { const sourceLibraryId = library("c"); const current = reference(sourceLibraryId, 2, 5, "stable"); const state = { schemaVersion: 1, references: [current] }; const base = { schemaVersion: 1, operation: "MARK_MISSING", sourceLibraryId, sourceLocator: current.sourceLocator, sourceSha256: current.sourceSha256, expectedGeneration: 2, expectedRevision: 5, }; assert.equal(missing.markLinkedLibraryMissing(state, { ...base, expectedRevision: 6 }).code, "REVISION_CONFLICT"); assert.equal(missing.markLinkedLibraryMissing(state, { ...base, sourceSha256: digest("different") }).code, "ASSET_SOURCE_HASH_MISMATCH"); assert.deepEqual(state.references, [current]); }); test("M12-03I rejects undeclared fields, duplicate identities, and invalid placeholders", () => { const sourceLibraryId = library("d"); const current = reference(sourceLibraryId, 1, 1, "invalid"); assert.throws(() => missing.parseLinkedMissingRequest({ schemaVersion: 1, operation: "MARK_MISSING", sourceLibraryId, sourceLocator: current.sourceLocator, sourceSha256: current.sourceSha256, expectedGeneration: 1, expectedRevision: 1, future: true, }), { code: "TASK_VALIDATION_FAILED" }); assert.throws(() => missing.parseLinkedMissingState({ schemaVersion: 1, references: [current, current] }), { code: "TASK_VALIDATION_FAILED" }); assert.throws(() => missing.parseLinkedLibraryReference({ ...current, status: "MISSING", placeholder: null }), { code: "TASK_VALIDATION_FAILED" }); assert.throws(() => missing.parseLinkedLibraryReference({ ...current, status: "AVAILABLE", placeholder: { kind: "MISSING_LIBRARY", sourceLibraryId, dataBlockIds: current.dataBlockIds } }), { code: "TASK_VALIDATION_FAILED" }); }); test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));