// media.test.mjs — the browser gate's own observation helper, gated // without a browser. // // node --test test/browser/media.test.mjs // // `observe` is the thing every media assertion in call.mjs now stands // on, and it is the one piece of the gate that can be wrong in a way // that makes the gate quieter rather than louder. The helper it // replaced was two early-return waits in a row — a deadline, not an // observation — and counters reading 0, 1, 2, 3 and then frozen for // ever satisfied it, because it stopped looking the moment it saw an // increase (Codex S4-14). The failure this slice is about is a tile // that runs for a moment after a renegotiation and then stops, so "it // moved once" is exactly the answer that must not count. // // Lifted out of call.mjs by name, the way the room.js tests lift room.js, // so what is under test is the shipped helper and not a copy of it. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; const callJS = fileURLToPath(new URL('./call.mjs', import.meta.url)); const src = readFileSync(callJS, 'utf8'); function lift(source, name) { let start = source.indexOf('function ' + name + '('); assert.notEqual(start, -1, name + ' is not in call.mjs any more — this test is stale, not passing'); if (source.slice(start - 6, start) === 'async ') start -= 6; let depth = 0; for (let i = source.indexOf('{', start); i < source.length; i++) { if (source[i] === '{') depth += 1; else if (source[i] === '}') { depth -= 1; if (depth === 0) return source.slice(start, i + 1); } } assert.fail('unbalanced braces reading ' + name + ' out of call.mjs'); } function readNumber(name) { const m = new RegExp('const ' + name + ' = (\\d+);').exec(src); assert.notEqual(m, null, name + ' is not a number constant in call.mjs any more'); return Number(m[1]); } const MEDIA_WINDOWS = readNumber('MEDIA_WINDOWS'); const MEDIA_WINDOW_MS = readNumber('MEDIA_WINDOW_MS'); // eslint-disable-next-line no-new-func const observe = new Function( 'MEDIA_WINDOWS', 'MEDIA_WINDOW_MS', 'sleep', lift(src, 'observe') + '\nreturn observe;', )(MEDIA_WINDOWS, MEDIA_WINDOW_MS, async () => {}); // counters plays a script: one entry per read, each a map of key to // total. Nothing sleeps — the helper's own waiting is stubbed out, so // these tests are about what it *requires*, not how long it takes. function counters(script) { let at = 0; return async () => script[Math.min(at++, script.length - 1)]; } test('the shape of an observation is what call.mjs ships', () => { assert.ok(MEDIA_WINDOWS >= 2, 'one window is a deadline, not an observation'); assert.ok(MEDIA_WINDOW_MS >= 1000, 'a window shorter than a second says little about video'); }); test('a stream that keeps arriving passes', async () => { const seen = await observe(counters([{ a: 0 }, { a: 10 }, { a: 20 }, { a: 30 }]), ['a']); assert.equal(seen.ok, true, JSON.stringify(seen)); assert.deepEqual(seen.stalled, []); assert.deepEqual(seen.series, ['a 0->10->20->30']); }); test('a burst and then a freeze does not pass', async () => { // The control, and Codex's own reproduction: 0, 1, 2, 3 and then 3 // for ever satisfied the helper this replaced. const seen = await observe(counters([{ a: 0 }, { a: 1 }, { a: 2 }, { a: 3 }, { a: 3 }, { a: 3 }]), ['a'], { windows: 5 }); assert.equal(seen.ok, false, 'a stream that stopped was called healthy'); assert.deepEqual(seen.stalled, [['a', 4]], 'the window it stopped in is what a reader needs'); }); test('a stream that stops in the last window does not pass', async () => { const seen = await observe(counters([{ a: 0 }, { a: 10 }, { a: 20 }, { a: 20 }]), ['a']); assert.equal(seen.ok, false); assert.deepEqual(seen.stalled, [['a', 3]]); }); test('a stream that stops and starts again does not pass', async () => { // Growth in every window, not growth overall: a tile that freezes for // a second and a half in the middle of a call is a tile that froze. const seen = await observe(counters([{ a: 0 }, { a: 10 }, { a: 10 }, { a: 30 }]), ['a']); assert.equal(seen.ok, false); assert.deepEqual(seen.stalled, [['a', 2]]); }); test('every stream is watched, and each is named when it stops', async () => { const seen = await observe(counters([ { a: 0, b: 0, c: 0 }, { a: 1, b: 1, c: 1 }, { a: 2, b: 1, c: 2 }, { a: 3, b: 1, c: 2 }, ]), ['a', 'b', 'c']); assert.equal(seen.ok, false); assert.deepEqual(seen.stalled, [['b', 2], ['c', 3]], 'one stream answered for another'); assert.equal(seen.series.length, 3); }); test('a counter that goes backwards is not growth', async () => { const seen = await observe(counters([{ a: 30 }, { a: 10 }, { a: 20 }, { a: 30 }]), ['a']); assert.equal(seen.ok, false); assert.deepEqual(seen.stalled, [['a', 1]]); }); test('expecting nothing is a failure, not a pass', async () => { // A check that walks the screens a page says it is showing would // otherwise pass vacuously for a screen that never arrived, which is // why the caller names its keys. const seen = await observe(counters([{}]), []); assert.equal(seen.ok, false); assert.equal(seen.series.length, 0); }); test('a stream nobody is sending reads as zero throughout, and fails', async () => { const seen = await observe(counters([{ a: 5 }, { a: 5 }, { a: 5 }, { a: 5 }]), ['a', 'missing']); assert.equal(seen.ok, false); assert.deepEqual(seen.stalled, [['a', 1], ['missing', 1]]); assert.deepEqual(seen.series, ['a 5->5->5->5', 'missing 0->0->0->0']); }); test('the observation waits the window it says it does', async () => { const waited = []; const timed = new Function( 'MEDIA_WINDOWS', 'MEDIA_WINDOW_MS', 'sleep', lift(src, 'observe') + '\nreturn observe;', )(MEDIA_WINDOWS, MEDIA_WINDOW_MS, async (ms) => { waited.push(ms); }); await timed(counters([{ a: 0 }, { a: 1 }, { a: 2 }, { a: 3 }]), ['a']); assert.deepEqual(waited, new Array(MEDIA_WINDOWS).fill(MEDIA_WINDOW_MS), 'the windows are not the duration the constants name'); });