// The live feed contract: the recorded observer reply validates; known-bad shapes are refused, each for its own reason. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { validate, CONTRACT } from './feed-contract.mjs'; const fixture = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'scene', 'fixtures', 'live-2026-10-07.json'), 'utf8')); const edit = fn => { const r = JSON.parse(JSON.stringify(fixture)); fn(r); return r; }; test('the recorded /api/live reply validates (905 blocks, 15 miners, 40 checkpoints)', () => { assert.deepEqual(validate(fixture), []); assert.equal(fixture.blocks.length, 905); assert.equal(fixture.miners.length, 15); assert.equal(fixture.finality.checkpoints.length, 40); }); test('the app\'s documented extensions are accepted', () => { const r = edit(r => { r.partial = true; r.state.source = 'observer'; r.state.you_blocks = 2; r.state.node = { peers: 3 }; }); assert.deepEqual(validate(r), []); }); test('known-bad: a block whose miner is the word "you" (the 0.3.19 app rewrote the field) is refused', () => { const p = validate(edit(r => { r.blocks[0].miner = 'you'; })); assert.match(p.join('\n'), /blocks\[0\]\.miner is not an 8-hex vote key id \("you"\)/); }); test('known-bad: now as a float of seconds (the 0.3.19 node fallback) is refused', () => { assert.match(validate(edit(r => { r.now = 1791301670.5; })).join('\n'), /now is not an ISO 8601 string/); }); test('known-bad: a missing proving object, a missing block key, an unknown key', () => { assert.match(validate(edit(r => { delete r.proving; })).join('\n'), /reply lacks proving/); assert.match(validate(edit(r => { delete r.blocks[3].rx; })).join('\n'), /blocks\[3\] lacks rx/); assert.match(validate(edit(r => { r.blocks[3].vote_key_hash = 'abc'; })).join('\n'), /blocks\[3\] carries an unknown key vote_key_hash/); assert.match(validate(edit(r => { r.miners[0] = { id: r.miners[0].id, vote_key_hash: 'x', blocks_10m: 1 }; })).join('\n'), /miners\[0\] lacks blocks/); }); test('known-bad: a colour, a shard state and a checkpoint state outside the enums', () => { assert.match(validate(edit(r => { r.blocks[0].color = 'winning'; })).join('\n'), /color is not one of blue, red, pending/); assert.match(validate(edit(r => { r.blocks[0].shards = [{ i: 0, n: 1, state: 'jackpot', prover: null, lag: null, payout: null, pgas: null }]; })).join('\n'), /state is not one of planned, proving, verified, paid/); assert.match(validate(edit(r => { r.finality.checkpoints[0].state = 'maybe'; })).join('\n'), /is not pending or locked/); }); test('the contract file lists what the Rust test reads', () => { for (const k of ['top', 'state', 'block', 'miner', 'finality', 'checkpoint', 'shard', 'proving_unsupported']) assert(Array.isArray(CONTRACT[k]) && CONTRACT[k].length > 0, k); });