Files
workinf_Blender_Wasm/web/tests/e2e/simulation-cache-identity.spec.ts
mes123456 0fe8d2bb56
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

116 lines
5.4 KiB
TypeScript

import { expect, test } from "@playwright/test";
test("M10-05 isolates Simulation caches by committed graph/source/revision identity", async ({ page }) => {
test.setTimeout(120_000);
await page.goto("/");
const result = await page.evaluate(async () => {
const { StorageClient } = await import("/src/storage/StorageClient.ts");
const digest = async (data: ArrayBuffer): Promise<string> => Array.from(
new Uint8Array(await crypto.subtle.digest("SHA-256", data)),
(byte) => byte.toString(16).padStart(2, "0"),
).join("");
const revisionHash = async (binding: {
graphId: string; graphHash: string; sourceBlendSha256: string; sourceRevision: number;
inputHash: string; blenderVersion: string; frameStart: number; frameEnd: number;
}): Promise<string> => digest(new TextEncoder().encode(JSON.stringify([
"blender-web-simulation-cache-revision-v2", binding.graphId, binding.graphHash,
binding.sourceBlendSha256, String(binding.sourceRevision), binding.inputHash,
binding.blenderVersion, String(binding.frameStart), String(binding.frameEnd),
])).buffer);
const errorCode = async (operation: () => Promise<unknown>): Promise<string> => {
try { await operation(); return ""; }
catch (error) { return String((error as Error & { code?: string }).code ?? ""); }
};
const projectId = `m10-05-${Date.now()}`;
const sourceOne = Uint8Array.from([0x42, 0x4c, 0x45, 0x4e, 0x44, 1]).buffer;
const sourceTwo = Uint8Array.from([0x42, 0x4c, 0x45, 0x4e, 0x44, 2]).buffer;
const payload = Uint8Array.from([11, 12, 13, 14]).buffer;
const frameHash = await digest(payload);
const common = {
graphId: "node-group:M10SimulationIdentity",
graphHash: await digest(Uint8Array.from([21]).buffer),
inputHash: await digest(Uint8Array.from([22]).buffer),
blenderVersion: "5.2.0",
frameStart: 1,
frameEnd: 1,
};
const makeManifest = async (source: ArrayBuffer, sourceRevision: number) => {
const binding = { ...common, sourceBlendSha256: await digest(source), sourceRevision };
return {
schemaVersion: 2 as const,
...binding,
revisionHash: await revisionHash(binding),
cacheSha256: frameHash,
byteLength: payload.byteLength,
frames: [{ frame: 1, byteOffset: 0, byteLength: payload.byteLength, sha256: frameHash }],
};
};
const firstManifest = await makeManifest(sourceOne, 7);
const first = new StorageClient();
await first.saveProject(projectId, 7, sourceOne.slice(0));
const storedOne = await first.putSimulationCache(projectId, firstManifest, payload.slice(0));
const forgedGraphCode = await errorCode(() => first.putSimulationCache(
projectId,
{ ...firstManifest, graphHash: "0".repeat(64) },
payload.slice(0),
));
const forgedSourceBinding = { ...firstManifest, sourceBlendSha256: "1".repeat(64) };
const forgedSourceManifest = {
...forgedSourceBinding,
revisionHash: await revisionHash(forgedSourceBinding),
};
const forgedSourceCode = await errorCode(() => first.putSimulationCache(
projectId,
forgedSourceManifest,
payload.slice(0),
));
first.terminate();
const restarted = new StorageClient();
await restarted.prepareSimulationCachePlayback(projectId, storedOne.cacheKey);
const recovered = await restarted.readSimulationCacheFrame(projectId, storedOne.cacheKey, 1);
const listedBefore = await restarted.listSimulationCaches(projectId);
await restarted.saveProject(projectId, 8, sourceTwo.slice(0));
const staleReadCode = await errorCode(() => restarted.readSimulationCache(projectId, storedOne.cacheKey));
const listedAfter = await restarted.listSimulationCaches(projectId);
const stalePutCode = await errorCode(() => restarted.putSimulationCache(
projectId,
firstManifest,
payload.slice(0),
));
const secondManifest = await makeManifest(sourceTwo, 8);
const storedTwo = await restarted.putSimulationCache(projectId, secondManifest, payload.slice(0));
const listedCurrent = await restarted.listSimulationCaches(projectId);
restarted.terminate();
return {
firstKey: storedOne.cacheKey,
secondKey: storedTwo.cacheKey,
recoveredBytes: Array.from(new Uint8Array(recovered.data)),
listedBefore: listedBefore.caches.map((cache) => cache.cacheKey),
listedAfter: listedAfter.caches.map((cache) => cache.cacheKey),
listedCurrent: listedCurrent.caches.map((cache) => cache.cacheKey),
forgedGraphCode,
forgedSourceCode,
staleReadCode,
stalePutCode,
sourceRevision: storedTwo.manifest.sourceRevision,
};
});
expect(result.firstKey).toMatch(/^sim2-[a-f0-9]{64}$/);
expect(result.secondKey).toMatch(/^sim2-[a-f0-9]{64}$/);
expect(result.secondKey).not.toBe(result.firstKey);
expect(result.recoveredBytes).toEqual([11, 12, 13, 14]);
expect(result.listedBefore).toEqual([result.firstKey]);
expect(result.listedAfter).toEqual([]);
expect(result.listedCurrent).toEqual([result.secondKey]);
expect(result.forgedGraphCode).toBe("SIMULATION_CACHE_REVISION_MISMATCH");
expect(result.forgedSourceCode).toBe("SIMULATION_CACHE_HASH_MISMATCH");
expect(result.staleReadCode).toBe("SIMULATION_CACHE_REVISION_MISMATCH");
expect(result.stalePutCode).toBe("SIMULATION_CACHE_REVISION_MISMATCH");
expect(result.sourceRevision).toBe(8);
});