import assert from "node:assert/strict"; 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 repoRoot = path.resolve(import.meta.dirname, "../../.."); const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "texture-paint-asset-unit-")); function transpile(sourceName, outputName, replacements = []) { const sourcePath = path.join(repoRoot, "web/protocol", sourceName); 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, []); const output = replacements.reduce((source, [from, to]) => source.replace(from, to), transpiled.outputText); fs.writeFileSync(path.join(temporary, outputName), output); } transpile("paint.ts", "paint.mjs"); transpile("texture-paint-asset.ts", "texture-paint-asset.mjs", [['from "./paint"', 'from "./paint.mjs"']]); const protocol = await import(pathToFileURL(path.join(temporary, "texture-paint-asset.mjs"))); const hash = "1".repeat(64); const target = { schemaVersion: 1, projectId: "texture-project", imageId: "image:Paint", textureAssetId: "image:Paint:tile:1001", kind: "PACKED", tile: 1001, revision: 4, width: 2, height: 2, mimeType: "image/png", colorSpace: "SRGB", sourcePath: "textures/paint.png", baseAssetSha256: hash, }; const patch = { schemaVersion: 1, textureAssetId: target.textureAssetId, tile: 1001, revision: 4, width: 2, height: 2, format: "RGBA8", colorSpace: "SRGB", baseSha256: "2".repeat(64), resultSha256: "3".repeat(64), byteOffset: 0, bytes: new Uint8Array([1, 2, 3, 255]), }; test("M9-11 binds a dirty range to one packed or UDIM tile identity", () => { const parsed = protocol.validateTexturePaintTileCommit({ schemaVersion: 1, target, patch }); assert.equal(parsed.target.kind, "PACKED"); assert.equal(parsed.patch.textureAssetId, target.textureAssetId); assert.equal(protocol.texturePaintTileBindingKey({ schemaVersion: 1, projectId: target.projectId, textureAssetId: target.textureAssetId, tile: 1001 }), "texture-paint:v1:texture-project:image%3APaint%3Atile%3A1001:1001"); }); test("M9-11 rejects target, tile, revision and path drift before storage", () => { assert.throws(() => protocol.validateTexturePaintTileCommit({ schemaVersion: 1, target, patch: { ...patch, tile: 1002 } }), /PAINT_SCHEMA_INVALID/); assert.throws(() => protocol.validateTexturePaintTileCommit({ schemaVersion: 1, target, patch: { ...patch, revision: 5 } }), /PAINT_SCHEMA_INVALID/); assert.throws(() => protocol.validateTexturePaintTileCommit({ schemaVersion: 1, target: { ...target, sourcePath: "../escape.png" }, patch }), /PAINT_SCHEMA_INVALID/); assert.throws(() => protocol.validateTexturePaintTileCommit({ schemaVersion: 1, target: { ...target, baseAssetSha256: "bad" }, patch }), /PAINT_SCHEMA_INVALID/); assert.throws(() => protocol.validateTexturePaintTileCommit({ schemaVersion: 1, target, patch, extra: true }), /PAINT_SCHEMA_INVALID/); });