// The page's integration and the native sink (wai/extensions/native-sink.md §10) are two // implementations of one text: `node --test web/native-sink.test.mjs` replays the native corpus's // `page-parity` case (wai/live-conformance/native-sink/) through `AuditSession`, call for call, and // requires the bundle the native sink signed, byte for byte, and its CMCD batch. import test from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import * as au from "./audit.js"; const doc = JSON.parse(readFileSync(new URL("../../live-conformance/native-sink/vectors.json", import.meta.url), "utf8")); const bytesOf = (name) => au.unhex(doc.objects[name]); /** energy-measurement §2.7's class bytes of a call's entry: the marker here. */ function classBytes(entry) { assert.ok(entry.energy_provenance === undefined || entry.energy_provenance === null, "the parity case states markers only"); return au.CLASS.UNMETERED; } test("the native sink's page-parity session, replayed by the page's integration, signs the same bundle", async () => { const c = doc.cases.find((x) => x.name === "page-parity"); assert.ok(c, "the corpus holds the page-parity case"); let clock = c.start_us; const key = await au.keyFromSeed(au.unhex(c.seed)); const s = await au.AuditSession.start({ channel: c.config.channel, owner: c.config.owner, windowUs: c.config.window_us, key, now: () => clock, cmcdEveryMs: 0 }); // The native session's log opens with `start` when the session does (native-sink §5.2). s.event("start", c.start_us); let bundle = null; for (const { do: d, answer } of c.calls) { switch (d.call) { case "playlist": assert.equal(d.receipt, null, "the page reads playlists, and checks no receipt"); s.playlist(new TextDecoder().decode(bytesOf(d.body)), d.url); break; case "object": { clock = d.meta.t_us; const r = await s.object({ url: d.meta.url, bytes: bytesOf(d.body), media: d.meta.media, at: d.meta.t_us, status: d.meta.status ?? 200, cmsdStatic: d.meta.cmsd_static ?? null, cmsdDynamic: d.meta.cmsd_dynamic ?? null }); if (answer.key) assert.equal(r, null, "a key is not acknowledged"); else assert.equal(au.hex(r), answer.sha256, "the object's SHA-256"); break; } case "event": s.event(d.name, d.t_us); break; case "observe": clock = d.obs.t_us; s.observe({ at: d.obs.t_us, playing: d.obs.playing, visibleBp: d.obs.visible_bp, audible: d.obs.audible, programMs: d.obs.program_ms, seeked: d.obs.seeked ?? false }); break; case "energy": s.energy(d.entry.role, d.entry.joules_micro, classBytes(d.entry)); break; case "cmcd": await Promise.all(s.pending); assert.equal(au.cmcdBatch(s.log), answer.cmcd, "the CMCD batch"); break; case "end": bundle = await s.close(d.ended, d.t_us); assert.equal(JSON.stringify(bundle), answer.bundle, "the bundle, byte for byte"); break; default: assert.fail(`the page has no call ${d.call}`); } } assert.ok(bundle, "the session ended"); assert.equal(c.outcome.outcome, "accept"); });