71 lines
4.3 KiB
JavaScript
71 lines
4.3 KiB
JavaScript
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(), "asset-preview-identity-unit-"));
|
|
const sourcePath = path.join(root, "web/protocol/asset-preview.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, "asset-preview.mjs"), transpiled.outputText);
|
|
const preview = await import(pathToFileURL(path.join(temporary, "asset-preview.mjs")));
|
|
const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-02B/manifest.json"), "utf8"));
|
|
const golden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-02B/identity.json"), "utf8"));
|
|
const sourceBytes = fs.readFileSync(path.join(root, manifest.artifacts.source.path));
|
|
const contentBytes = fs.readFileSync(path.join(root, manifest.artifacts.content.path));
|
|
const sha256 = (value) => crypto.createHash("sha256").update(value).digest("hex");
|
|
const stableJSON = (value) => Array.isArray(value)
|
|
? `[${value.map(stableJSON).join(",")}]`
|
|
: value && typeof value === "object"
|
|
? `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJSON(value[key])}`).join(",")}}`
|
|
: JSON.stringify(value);
|
|
|
|
test("M12-02B binds source, content, generator, protocol, and settings artifacts", () => {
|
|
assert.equal(manifest.task, "M12-02B");
|
|
assert.equal(manifest.enablingTask, true);
|
|
assert.equal(manifest.parityStateChange, false);
|
|
assert.equal(manifest.nextTask, "M12-02C");
|
|
for (const artifact of Object.values(manifest.artifacts)) {
|
|
assert.equal(sha256(fs.readFileSync(path.join(root, artifact.path))), artifact.sha256, artifact.path);
|
|
}
|
|
assert.equal(sha256(stableJSON(manifest.generatorSettings)), manifest.generatorSettingsSha256);
|
|
assert.equal(manifest.generatorSettingsSha256, golden.generator.settingsSha256);
|
|
});
|
|
|
|
test("M12-02B creates and parses the canonical preview identity", async () => {
|
|
const { identitySha256, ...base } = golden;
|
|
assert.deepEqual(await preview.createAssetPreviewIdentity(base), golden);
|
|
assert.deepEqual(await preview.parseAssetPreviewIdentity(golden), golden);
|
|
assert.equal(identitySha256, await preview.computeAssetPreviewIdentity(base));
|
|
assert.notEqual(golden.source.sha256, golden.content.sha256);
|
|
assert.deepEqual(await preview.verifyAssetPreviewIdentity(
|
|
golden,
|
|
sourceBytes.buffer.slice(sourceBytes.byteOffset, sourceBytes.byteOffset + sourceBytes.byteLength),
|
|
contentBytes.buffer.slice(contentBytes.byteOffset, contentBytes.byteOffset + contentBytes.byteLength),
|
|
golden.generator,
|
|
), golden);
|
|
});
|
|
|
|
test("M12-02B rejects independent source, content, generator, dimensions, and unknown-field drift", async () => {
|
|
const source = sourceBytes.buffer.slice(sourceBytes.byteOffset, sourceBytes.byteOffset + sourceBytes.byteLength);
|
|
const content = contentBytes.buffer.slice(contentBytes.byteOffset, contentBytes.byteOffset + contentBytes.byteLength);
|
|
const changedSource = source.slice(0); new Uint8Array(changedSource)[0] ^= 1;
|
|
const changedContent = content.slice(0); new Uint8Array(changedContent)[0] ^= 1;
|
|
await assert.rejects(preview.verifyAssetPreviewIdentity(golden, changedSource, content, golden.generator), { code: "ASSET_SOURCE_HASH_MISMATCH" });
|
|
await assert.rejects(preview.verifyAssetPreviewIdentity(golden, source, changedContent, golden.generator), { code: "ASSET_SOURCE_HASH_MISMATCH" });
|
|
await assert.rejects(preview.verifyAssetPreviewIdentity(golden, source, content, { ...golden.generator, settingsSha256: "f".repeat(64) }), { code: "ASSET_PREVIEW_IDENTITY_MISMATCH" });
|
|
await assert.rejects(preview.parseAssetPreviewIdentity({ ...golden, content: { ...golden.content, width: 9 } }), { code: "ASSET_PREVIEW_IDENTITY_MISMATCH" });
|
|
await assert.rejects(preview.parseAssetPreviewIdentity({ ...golden, future: true }), { code: "ASSET_MANIFEST_INVALID" });
|
|
});
|
|
|
|
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));
|