Advance M8-M11 parity workflows
This commit is contained in:
165
web/tests/unit/external-vfont.test.mjs
Normal file
165
web/tests/unit/external-vfont.test.mjs
Normal file
@@ -0,0 +1,165 @@
|
||||
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 repoRoot = path.resolve(import.meta.dirname, "../../..");
|
||||
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "external-vfont-unit-"));
|
||||
for (const name of ["asset-path", "external-vfont"]) {
|
||||
const sourcePath = path.join(repoRoot, `web/protocol/${name}.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, `${name}.mjs`), transpiled.outputText.replace('"./asset-path"', '"./asset-path.mjs"'));
|
||||
}
|
||||
const font = await import(pathToFileURL(path.join(temporary, "external-vfont.mjs")));
|
||||
const importSourcePath = path.join(repoRoot, "web/app/src/fonts/external-vfont-import.ts");
|
||||
const importTranspiled = ts.transpileModule(fs.readFileSync(importSourcePath, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
|
||||
fileName: importSourcePath,
|
||||
reportDiagnostics: true,
|
||||
});
|
||||
assert.deepEqual(importTranspiled.diagnostics, []);
|
||||
fs.writeFileSync(path.join(temporary, "external-vfont-import.mjs"), importTranspiled.outputText.replace('"../../../protocol/external-vfont"', '"./external-vfont.mjs"'));
|
||||
const importer = await import(pathToFileURL(path.join(temporary, "external-vfont-import.mjs")));
|
||||
const pfb = fs.readFileSync(path.join(repoRoot, "blender-5.2.0/release/datafiles/bfont.pfb"));
|
||||
const data = pfb.buffer.slice(pfb.byteOffset, pfb.byteOffset + pfb.byteLength);
|
||||
const digest = crypto.createHash("sha256").update(pfb).digest("hex");
|
||||
const request = { sourcePath: "//fonts/bfont.pfb", mimeType: "application/x-font-type1", byteLength: pfb.byteLength, sha256: digest, data };
|
||||
|
||||
test("M9-01 validates a real project-relative PFB before storage or Main mutation", async () => {
|
||||
const protocolSource = fs.readFileSync(path.join(repoRoot, "web/protocol/external-vfont.ts"), "utf8");
|
||||
assert.doesNotMatch(protocolSource, /StorageClient|WebEngineClient|storage\.worker|web-engine\.worker/);
|
||||
const validated = await font.validateExternalVFontImport(request);
|
||||
assert.deepEqual({ ...validated, data: undefined }, {
|
||||
schemaVersion: 1,
|
||||
sourcePath: "//fonts/bfont.pfb",
|
||||
fileName: "bfont.pfb",
|
||||
format: "PFB",
|
||||
mimeType: "application/x-font-type1",
|
||||
byteLength: 25181,
|
||||
sha256: "a33954fdab9fb09b9d308cb7f970518293128922ffc523c0a22b3b314a9a56c6",
|
||||
data: undefined,
|
||||
});
|
||||
assert.notEqual(validated.data, request.data);
|
||||
assert.deepEqual(new Uint8Array(validated.data), new Uint8Array(request.data));
|
||||
});
|
||||
|
||||
test("M9-01 blocks path escape, type spoofing, size overflow and hash drift", async () => {
|
||||
const cases = [
|
||||
[{ ...request, sourcePath: "../../outside.pfb" }, "NON_MESH_RESOURCE_OUTSIDE_PROJECT"],
|
||||
[{ ...request, sourcePath: "//assets/bfont.pfb" }, "NON_MESH_RESOURCE_OUTSIDE_PROJECT"],
|
||||
[{ ...request, sourcePath: "//fonts/bfont.ttf", mimeType: "font/ttf" }, "NON_MESH_BINARY_INVALID"],
|
||||
[{ ...request, mimeType: "font/otf" }, "NON_MESH_BINARY_INVALID"],
|
||||
[{ ...request, byteLength: font.EXTERNAL_VFONT_MAX_BYTES + 1 }, "NON_MESH_DATA_BUDGET_EXCEEDED"],
|
||||
[{ ...request, byteLength: request.byteLength - 1 }, "NON_MESH_BINARY_INVALID"],
|
||||
[{ ...request, sha256: "0".repeat(64) }, "ASSET_SOURCE_HASH_MISMATCH"],
|
||||
];
|
||||
for (const [value, code] of cases) await assert.rejects(font.validateExternalVFontImport(value), (error) => error.code === code);
|
||||
});
|
||||
|
||||
test("M9-02 accepts only a matching OPFS content-addressed receipt before Main import", async () => {
|
||||
const validated = await font.validateExternalVFontImport(request);
|
||||
const stored = {
|
||||
assetId: `sha256:${digest}`,
|
||||
projectId: "m9-vfont-unit",
|
||||
sha256: digest,
|
||||
bytes: pfb.byteLength,
|
||||
mimeType: request.mimeType,
|
||||
sourcePath: "fonts/bfont.pfb",
|
||||
path: `projects/m9-vfont-unit/assets/sha256/${digest.slice(0, 2)}/${digest}`,
|
||||
createdAt: "2026-08-16T00:00:00.000Z",
|
||||
lastAccessAt: "2026-08-16T00:00:00.000Z",
|
||||
persisted: true,
|
||||
deduplicated: false,
|
||||
};
|
||||
const mainImport = font.createExternalVFontMainImport(validated, stored);
|
||||
assert.deepEqual({ ...mainImport, data: undefined }, {
|
||||
schemaVersion: 1,
|
||||
projectId: stored.projectId,
|
||||
assetId: stored.assetId,
|
||||
assetPath: stored.path,
|
||||
sourcePath: "//fonts/bfont.pfb",
|
||||
name: "bfont",
|
||||
format: "PFB",
|
||||
mimeType: request.mimeType,
|
||||
byteLength: pfb.byteLength,
|
||||
sha256: digest,
|
||||
data: undefined,
|
||||
});
|
||||
assert.notEqual(mainImport.data, validated.data);
|
||||
for (const receipt of [
|
||||
{ ...stored, persisted: false },
|
||||
{ ...stored, sha256: "0".repeat(64) },
|
||||
{ ...stored, bytes: stored.bytes - 1 },
|
||||
{ ...stored, path: `indexeddb:${stored.projectId}:${digest}` },
|
||||
]) {
|
||||
assert.throws(() => font.createExternalVFontMainImport(validated, receipt), (error) =>
|
||||
error.code === "ASSET_SOURCE_HASH_MISMATCH" || error.code === "NON_MESH_RESOURCE_MISSING");
|
||||
}
|
||||
});
|
||||
|
||||
test("M9-03 verifies the project asset before one Main font-style replacement", async () => {
|
||||
const stored = {
|
||||
asset: {
|
||||
assetId: `sha256:${digest}`,
|
||||
projectId: "m9-vfont-unit",
|
||||
sha256: digest,
|
||||
bytes: pfb.byteLength,
|
||||
mimeType: request.mimeType,
|
||||
sourcePath: "fonts/bfont.pfb",
|
||||
path: `projects/m9-vfont-unit/assets/sha256/${digest.slice(0, 2)}/${digest}`,
|
||||
createdAt: "2026-08-16T00:00:00.000Z",
|
||||
lastAccessAt: "2026-08-16T00:00:00.000Z",
|
||||
},
|
||||
data: data.slice(0),
|
||||
};
|
||||
const vfont = { id: "vfont:bfont", name: "bfont", sourcePath: "//fonts/bfont.pfb", builtin: false, packed: true, packedByteLength: pfb.byteLength, sha256: digest };
|
||||
const original = { regular: "vfont:Bfont", bold: "vfont:Bfont", italic: "vfont:Bfont", boldItalic: "vfont:Bfont" };
|
||||
const snapshot = { nonMeshData: [{ id: "data:font", type: "FONT", fontLinks: original }], vfonts: [vfont] };
|
||||
const events = [];
|
||||
const result = await importer.replaceExternalVFontStyleInMain({
|
||||
projectId: stored.asset.projectId,
|
||||
sha256: digest,
|
||||
dataId: "data:font",
|
||||
vfontId: vfont.id,
|
||||
style: "regular",
|
||||
snapshot,
|
||||
storage: { readAsset: async () => { events.push("storage:verified"); return stored; } },
|
||||
engine: { applyCommand: async (command) => {
|
||||
events.push("main:committed");
|
||||
return { snapshot: { ...snapshot, nonMeshData: [{ ...snapshot.nonMeshData[0], fontLinks: command.links }] } };
|
||||
} },
|
||||
});
|
||||
assert.deepEqual(events, ["storage:verified", "main:committed"]);
|
||||
assert.deepEqual(result.previousLinks, original);
|
||||
assert.equal(result.links.regular, vfont.id);
|
||||
});
|
||||
|
||||
test("M9-03 blocks a missing project asset before Main replacement", async () => {
|
||||
let mainCalls = 0;
|
||||
const snapshot = {
|
||||
nonMeshData: [{ id: "data:font", type: "FONT", fontLinks: { regular: "vfont:Bfont", bold: "vfont:Bfont", italic: "vfont:Bfont", boldItalic: "vfont:Bfont" } }],
|
||||
vfonts: [{ id: "vfont:bfont", name: "bfont", sourcePath: "//fonts/bfont.pfb", builtin: false, packed: true, packedByteLength: pfb.byteLength, sha256: digest }],
|
||||
};
|
||||
await assert.rejects(importer.replaceExternalVFontStyleInMain({
|
||||
projectId: "m9-vfont-unit",
|
||||
sha256: digest,
|
||||
dataId: "data:font",
|
||||
vfontId: "vfont:bfont",
|
||||
style: "regular",
|
||||
snapshot,
|
||||
storage: { readAsset: async () => { const error = new Error("missing"); error.code = "NON_MESH_RESOURCE_MISSING"; throw error; } },
|
||||
engine: { applyCommand: async () => { mainCalls += 1; throw new Error("unexpected Main call"); } },
|
||||
}), (error) => error.code === "NON_MESH_RESOURCE_MISSING");
|
||||
assert.equal(mainCalls, 0);
|
||||
});
|
||||
|
||||
test.after(() => fs.rmSync(temporary, { recursive: true, force: true }));
|
||||
Reference in New Issue
Block a user