Advance M8-M11 parity workflows
This commit is contained in:
94
web/tests/e2e/sequencer-media-revision.spec.ts
Normal file
94
web/tests/e2e/sequencer-media-revision.spec.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const codecGolden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-09/sequencer-codec-probe.json"), "utf8")) as {
|
||||
assets: Array<{ stripType: "IMAGE" | "SOUND" | "MOVIE"; mimeType: string; path: string; byteLength: number; sha256: string }>;
|
||||
};
|
||||
const revisionGolden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-11/sequencer-media-revision.json"), "utf8")) as {
|
||||
sourceSha256: string;
|
||||
decisions: Array<[string, number, "PUBLISH" | "STALE", string | null]>;
|
||||
published: string[];
|
||||
cacheWrites: string[];
|
||||
};
|
||||
|
||||
test("M11-11 gates late seek, scrub and real decode results before publish or cache", async ({ page }) => {
|
||||
const movie = codecGolden.assets.find((asset) => asset.stripType === "MOVIE")!;
|
||||
const bytes = fs.readFileSync(path.join(root, movie.path));
|
||||
expect(crypto.createHash("sha256").update(bytes).digest("hex")).toBe(revisionGolden.sourceSha256);
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(async ({ movie, bytes }) => {
|
||||
const [probeModule, revisionModule] = await Promise.all([
|
||||
import("/src/sequencer/SequencerCodecProbe.ts"),
|
||||
import("/src/sequencer/SequencerMediaRevisionGate.ts"),
|
||||
]);
|
||||
const gate = new revisionModule.SequencerMediaRevisionGate("sequencer:main", 7);
|
||||
const published: string[] = [];
|
||||
const cacheWrites: string[] = [];
|
||||
const decisions: Array<[string, number, string, string | null]> = [];
|
||||
const complete = (
|
||||
request: import("/src/sequencer/SequencerMediaRevisionGate.ts").SequencerMediaRevisionRequestIR,
|
||||
payloadSha256 = movie.sha256,
|
||||
) => ({ ...request, status: "COMPLETED" as const, sourceFrame: request.frame, payloadSha256 });
|
||||
const resolve = (
|
||||
request: import("/src/sequencer/SequencerMediaRevisionGate.ts").SequencerMediaRevisionRequestIR,
|
||||
response = complete(request),
|
||||
) => {
|
||||
const decision = gate.resolve(request, response, (accepted) => {
|
||||
const label = `${accepted.operation}@${accepted.requestRevision}`;
|
||||
published.push(label);
|
||||
if (accepted.operation === "DECODE") cacheWrites.push(label);
|
||||
});
|
||||
decisions.push([decision.operation, decision.requestRevision, decision.status, decision.code]);
|
||||
return decision;
|
||||
};
|
||||
|
||||
const oldSeek = gate.begin("SEEK", 10);
|
||||
const oldSeekResult = new Promise<ReturnType<typeof complete>>((resolveResult) =>
|
||||
setTimeout(() => resolveResult(complete(oldSeek)), 30));
|
||||
const currentScrub = gate.begin("SCRUB", 20);
|
||||
resolve(currentScrub);
|
||||
resolve(oldSeek, await oldSeekResult);
|
||||
|
||||
const oldDecode = gate.begin("DECODE", 30);
|
||||
const request = probeModule.createSequencerCodecProbeRequest("MOVIE", movie.mimeType, movie.byteLength, movie.sha256);
|
||||
const source = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
||||
const lateDecode = new Promise<Awaited<ReturnType<typeof probeModule.probeSequencerCodec>>>((resolveReceipt) =>
|
||||
setTimeout(() => { void probeModule.probeSequencerCodec(request, source.slice(0)).then(resolveReceipt); }, 20));
|
||||
const replacedState = gate.replaceTimeline("sequencer:main", 8);
|
||||
const lateReceipt = await lateDecode;
|
||||
resolve(oldDecode, complete(oldDecode, lateReceipt.sourceSha256));
|
||||
|
||||
const currentDecode = gate.begin("DECODE", 40);
|
||||
const currentReceipt = await probeModule.probeSequencerCodec(request, source.slice(0));
|
||||
resolve(currentDecode, complete(currentDecode, currentReceipt.sourceSha256));
|
||||
|
||||
const forgedSeek = gate.begin("SEEK", 50);
|
||||
resolve(forgedSeek, { ...complete(forgedSeek), requestId: "media:forged" });
|
||||
|
||||
let monotonicCode = "";
|
||||
try { gate.replaceTimeline("sequencer:main", 8); }
|
||||
catch (error) { monotonicCode = (error as { code?: string }).code ?? String(error); }
|
||||
return {
|
||||
decisions,
|
||||
published,
|
||||
cacheWrites,
|
||||
replacedState,
|
||||
finalState: gate.state(),
|
||||
lateReceipt: { status: lateReceipt.status, backend: lateReceipt.backend },
|
||||
currentReceipt: { status: currentReceipt.status, backend: currentReceipt.backend },
|
||||
monotonicCode,
|
||||
};
|
||||
}, { movie, bytes: new Uint8Array(bytes) });
|
||||
|
||||
expect(result.decisions).toEqual(revisionGolden.decisions);
|
||||
expect(result.published).toEqual(revisionGolden.published);
|
||||
expect(result.cacheWrites).toEqual(revisionGolden.cacheWrites);
|
||||
expect(result.replacedState).toEqual({ schemaVersion: 1, timelineId: "sequencer:main", timelineRevision: 8, latestRequestRevision: 4 });
|
||||
expect(result.finalState).toEqual({ schemaVersion: 1, timelineId: "sequencer:main", timelineRevision: 8, latestRequestRevision: 6 });
|
||||
expect(result.lateReceipt).toEqual({ status: "READY", backend: "HTML_MEDIA" });
|
||||
expect(result.currentReceipt).toEqual({ status: "READY", backend: "HTML_MEDIA" });
|
||||
expect(result.monotonicCode).toBe("REVISION_CONFLICT");
|
||||
});
|
||||
Reference in New Issue
Block a user