94 lines
4.7 KiB
TypeScript
94 lines
4.7 KiB
TypeScript
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
const root = path.resolve(import.meta.dirname, "../../..");
|
|
const fixtures = JSON.parse(process.env.WBV2_BATCH_FIXTURES ?? JSON.stringify(process.env.WBV2_BATCH_FIXTURE ? [process.env.WBV2_BATCH_FIXTURE] : [])) as string[];
|
|
const task = process.env.WBV2_BATCH_TASK ?? "WBV2-P1152-001";
|
|
const productionPath = process.env.WBV2_BATCH_PRODUCTION ?? `tests/golden/${task}/modifier-reader-batch-production.json`;
|
|
const expectedTypes = JSON.parse(process.env.WBV2_BATCH_TYPES ?? "[]") as string[];
|
|
const sourceUnits = JSON.parse(process.env.WBV2_BATCH_UNITS ?? "[]") as string[];
|
|
if (!fixtures.length || !expectedTypes.length || !sourceUnits.length) throw new Error("multi-fixture batch requires fixtures, expected types and source units");
|
|
const fixtureBytes = Object.fromEntries(fixtures.map((name) => [name, Array.from(fs.readFileSync(path.join(root, name)))]));
|
|
const fixtureSha256 = Object.fromEntries(fixtures.map((name) => [name, crypto.createHash("sha256").update(Buffer.from(fixtureBytes[name])).digest("hex")]));
|
|
|
|
test("multi-fixture modifier reader batch preserves Blender Main metadata", async ({ page }) => {
|
|
test.setTimeout(180_000);
|
|
await page.goto("/");
|
|
const result = await page.evaluate(async (input) => {
|
|
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
|
const canonical = (snapshot: any) => [...(snapshot.meshes ?? []), ...(snapshot.nodes ?? []), ...(snapshot.volumes ?? []), ...(snapshot.objects ?? [])].flatMap((mesh: any) =>
|
|
(mesh.modifierStack ?? []).map((modifier: any) => ({
|
|
meshId: mesh.id,
|
|
uuid: modifier.uuid,
|
|
name: modifier.name,
|
|
type: modifier.type,
|
|
enabled: modifier.enabled,
|
|
evaluationStatus: modifier.evaluationStatus,
|
|
parameters: modifier.parameters ?? null,
|
|
})));
|
|
const output: Record<string, any> = {};
|
|
for (const [name, bytes] of Object.entries(input.fixtures as Record<string, number[]>)) {
|
|
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
|
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
|
const secondClient = new WebEngineClient({ timeoutMs: 60_000 });
|
|
try {
|
|
const opened = await client.openBlend(Uint8Array.from(bytes).buffer);
|
|
const before = canonical(opened.snapshot);
|
|
const saved = await client.saveBlend();
|
|
const after = canonical((await reopenedClient.openBlend(saved.slice(0))).snapshot);
|
|
const second = canonical((await secondClient.openBlend(Uint8Array.from(bytes).buffer)).snapshot);
|
|
output[name] = {
|
|
before,
|
|
types: before.map((modifier: any) => modifier.type),
|
|
after,
|
|
second,
|
|
saveReopenExact: JSON.stringify(before) === JSON.stringify(after),
|
|
deterministicExact: JSON.stringify(before) === JSON.stringify(second),
|
|
savedSha256: await crypto.subtle.digest("SHA-256", saved).then((hash) =>
|
|
Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("")),
|
|
};
|
|
try {
|
|
await client.openBlend(Uint8Array.from([0x42, 0x4c, 0x45, 0x4e, 0x44]).buffer);
|
|
output[name].malformed = { code: "ACCEPTED_MALFORMED" };
|
|
} catch (error: any) {
|
|
output[name].malformed = { code: error?.code, message: error?.message };
|
|
}
|
|
} catch (error: any) {
|
|
output[name] = { error: { code: error?.code, message: error?.message, detail: String(error) } };
|
|
} finally {
|
|
client.terminate();
|
|
reopenedClient.terminate();
|
|
secondClient.terminate();
|
|
}
|
|
}
|
|
return output;
|
|
}, { fixtures: fixtureBytes });
|
|
|
|
const report = {
|
|
schemaVersion: 1,
|
|
task,
|
|
operation: "MODIFIER_READER_MULTI_FIXTURE_CHROMIUM_SAVE_REOPEN",
|
|
browser: "chromium",
|
|
queueMutation: false,
|
|
fixtures,
|
|
fixtureSha256,
|
|
expectedTypes,
|
|
sourceUnits,
|
|
...result,
|
|
};
|
|
fs.mkdirSync(path.join(root, `tests/golden/${task}`), { recursive: true });
|
|
fs.mkdirSync(path.dirname(path.join(root, productionPath)), { recursive: true });
|
|
fs.writeFileSync(path.join(root, productionPath), `${JSON.stringify(report, null, 2)}\n`);
|
|
|
|
const observedTypes = new Set(Object.values(result).flatMap((entry: any) => entry?.types ?? []));
|
|
for (const type of expectedTypes) expect(observedTypes.has(type)).toBe(true);
|
|
for (const fixture of fixtures) {
|
|
expect(result[fixture]?.error).toBeUndefined();
|
|
expect(result[fixture]?.saveReopenExact).toBe(true);
|
|
expect(result[fixture]?.deterministicExact).toBe(true);
|
|
expect(result[fixture]?.malformed?.code).not.toBe("ACCEPTED_MALFORMED");
|
|
}
|
|
});
|