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 golden = 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; sourcePath: string; byteLength: number; sha256: string; backend: string; decoded: Record; }>; movieGenerator: { path: string; sha256: string }; blockedCode: string; }; test("M11-09 probes IMAGE, SOUND and MOVIE bytes at runtime without extension guessing", async ({ page }) => { const assets = golden.assets.map((asset) => { const bytes = fs.readFileSync(path.join(root, asset.path)); expect(bytes.byteLength).toBe(asset.byteLength); expect(crypto.createHash("sha256").update(bytes).digest("hex")).toBe(asset.sha256); return { ...asset, bytes: new Uint8Array(bytes) }; }); expect(crypto.createHash("sha256").update(fs.readFileSync(path.join(root, golden.movieGenerator.path))).digest("hex")) .toBe(golden.movieGenerator.sha256); await page.goto("/"); const result = await page.evaluate(async ({ assets }) => { const [runtime, protocol] = await Promise.all([ import("/src/sequencer/SequencerCodecProbe.ts"), import("/src/sequencer/SequencerTimeline.ts"), ]); const ready = []; for (const asset of assets) { const request = runtime.createSequencerCodecProbeRequest(asset.stripType, asset.mimeType, asset.byteLength, asset.sha256); const receipt = await runtime.probeSequencerCodec(request, asset.bytes.buffer); const gate = protocol.gateSequencerCodec(request, receipt); ready.push({ sourcePath: asset.sourcePath, receipt, gate: gate.status }); } const source = assets[0]; const corrupted = source.bytes.slice(); corrupted.fill(0, 0, Math.min(32, corrupted.length)); const corruptedHash = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", corrupted.buffer)), (byte) => byte.toString(16).padStart(2, "0")).join(""); const corruptRequest = runtime.createSequencerCodecProbeRequest("IMAGE", "image/png", corrupted.byteLength, corruptedHash); const corruptReceipt = await runtime.probeSequencerCodec(corruptRequest, corrupted.buffer); const mismatchRequest = runtime.createSequencerCodecProbeRequest("IMAGE", "image/png", source.byteLength, source.sha256); const mismatchReceipt = await runtime.probeSequencerCodec(mismatchRequest, corrupted.buffer); const spoofRequest = runtime.createSequencerCodecProbeRequest("MOVIE", "video/mp4", source.byteLength, source.sha256); const spoofReceipt = await runtime.probeSequencerCodec(spoofRequest, source.bytes.buffer); const forgedReceipt = { ...ready[0].receipt, sourceSha256: "f".repeat(64) }; const forgedGate = protocol.gateSequencerCodec(mismatchRequest, forgedReceipt); return { ready, corrupt: { status: corruptReceipt.status, reason: corruptReceipt.reason }, mismatch: { status: mismatchReceipt.status, reason: mismatchReceipt.reason }, spoof: { status: spoofReceipt.status, reason: spoofReceipt.reason }, forged: { status: forgedGate.status, code: forgedGate.issues[0]?.code }, }; }, { assets }); expect(result.ready.map((item) => ({ sourcePath: item.sourcePath, status: item.receipt.status, backend: item.receipt.backend, decoded: item.receipt.decoded, gate: item.gate, }))).toEqual(golden.assets.map((asset) => ({ sourcePath: asset.sourcePath, status: "READY", backend: asset.backend, decoded: asset.decoded, gate: "READY", }))); expect(result.corrupt).toEqual({ status: "BLOCKED", reason: "DECODE_FAILED" }); expect(result.mismatch).toEqual({ status: "BLOCKED", reason: "SOURCE_IDENTITY_MISMATCH" }); expect(result.spoof.status).toBe("BLOCKED"); expect(result.forged).toEqual({ status: "BLOCKED", code: golden.blockedCode }); });