import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { FEED_STATE_SEVERITY, LAYER_FEED_STATE_LABELS, LAYER_FEED_STATES, SNAPSHOT_FEED_STATES, feedProvenanceEnvelope, feedProvenanceNote, layerFeedState, layerSnapshot, layerSnapshots, provenanceTag, snapshotAgeLabel, snapshotAgeSec, viewStateLayerRow, worstFeedState, } from './layerSnapshot.js'; const NOW = 1_700_000_000_000; test('layerSnapshot stays Cesium-free so the HUD proxy can import it', () => { const src = readFileSync( new URL('./layerSnapshot.js', import.meta.url), 'utf8', ); assert.equal(src.includes("from './manager.js'"), false); assert.doesNotMatch(src, /from ['"]cesium['"]/i); }); test('layerFeedState matches the chip classifier for every honesty class', () => { assert.equal( layerFeedState({ error: 'feed down', count: 0, lastUpdate: null }), 'unavailable', ); assert.equal( layerFeedState({ status: 'unavailable', error: 'feed down', count: 50, lastUpdate: 1, }), 'unavailable', ); assert.equal( layerFeedState({ mode: 'sim', count: 100, lastUpdate: 1 }), 'fallback', ); assert.equal( layerFeedState({ source: 'adsb.lol', count: 10, lastUpdate: 1 }), 'fallback', ); assert.equal( layerFeedState({ source: 'adsb.lol', fallback: false, count: 10, lastUpdate: 1, }), 'nominal', ); assert.equal( layerFeedState({ stale: true, count: 0, lastUpdate: 1 }), 'stale', ); assert.equal( layerFeedState({ error: 'partial group failure', count: 50, lastUpdate: 1, }), 'degraded', ); assert.equal(layerFeedState({ loading: true }), 'loading'); assert.equal(layerFeedState({ count: 5, lastUpdate: 1 }), 'nominal'); assert.equal( layerFeedState({ status: 'zoom-in', error: 'zoom in to search', count: 12, }), 'nominal', ); assert.equal( layerFeedState({ status: 'zoom-in', stale: true, count: 12 }), 'stale', ); }); test('chip labels and severity cover every feed-state the envelope can emit', () => { assert.deepEqual( [...LAYER_FEED_STATES], [ 'nominal', 'loading', 'degraded', 'stale', 'partial', 'fallback', 'unavailable', ], ); assert.deepEqual([...SNAPSHOT_FEED_STATES], [...LAYER_FEED_STATES, 'off']); for (const state of LAYER_FEED_STATES) { assert.equal(typeof LAYER_FEED_STATE_LABELS[state], 'string'); assert.equal(typeof FEED_STATE_SEVERITY[state], 'number'); } assert.equal( FEED_STATE_SEVERITY.unavailable < FEED_STATE_SEVERITY.stale, true, ); assert.equal(FEED_STATE_SEVERITY.stale < FEED_STATE_SEVERITY.fallback, true); assert.equal(worstFeedState(['nominal', 'stale', 'fallback']), 'stale'); assert.equal(worstFeedState(['degraded', 'unavailable']), 'unavailable'); assert.equal(worstFeedState([]), null); }); test('partial is a feed-state of its own: above nominal, below every fault', () => { const layer = (id, stats) => ({ id, name: id, enabled: true, stats }); const partial = layer('flights', { partial: true, count: 4, lastUpdate: NOW, }); const nominal = layer('vessels', { count: 9, lastUpdate: NOW }); const stale = layer('cctv', { stale: true, count: 2, lastUpdate: NOW }); const overall = (layers) => feedProvenanceEnvelope(layers, { now: NOW }).overall; assert.equal(overall([partial]), 'partial'); assert.equal(overall([partial, nominal]), 'partial'); assert.equal(overall([nominal, partial]), 'partial'); assert.equal(overall([partial, stale]), 'stale'); assert.equal(overall([stale, partial]), 'stale'); assert.equal(LAYER_FEED_STATE_LABELS.partial, 'PARTIAL'); const note = feedProvenanceEnvelope([partial], { now: NOW }).note; assert.match(note, /coverage is incomplete/); assert.doesNotMatch(note, /do not describe this as live/); assert.equal( FEED_STATE_SEVERITY.fallback < FEED_STATE_SEVERITY.partial && FEED_STATE_SEVERITY.partial < FEED_STATE_SEVERITY.nominal, true, ); }); test('a disabled layer is off even when leftover stats would read stale', () => { const snap = layerSnapshot( { id: 'flights', name: 'Live Flights', enabled: false, source: 'OpenSky Network', stats: { stale: true, count: 12, lastUpdate: NOW - 240_000, source: 'OpenSky Network', }, }, { now: NOW }, ); assert.equal(snap.feedState, 'off'); assert.equal(snap.enabled, false); assert.equal(snap.count, 12); assert.equal(snap.ageLabel, '4m ago'); }); test('an enabled stale layer carries age, source, and a mechanical note', () => { const snap = layerSnapshot( { id: 'flights', name: 'Live Flights', enabled: true, stats: { stale: true, count: 12, lastUpdate: NOW - 240_000, source: 'OpenSky Network', }, }, { now: NOW }, ); assert.equal(snap.feedState, 'stale'); assert.equal(snap.source, 'OpenSky Network'); assert.equal(snap.ageSec, 240); assert.equal(snap.ageLabel, '4m ago'); const envelope = feedProvenanceEnvelope([snap], { now: NOW }); assert.equal(envelope.overall, 'stale'); assert.match(envelope.note, /Live Flights is STALE/); assert.match(envelope.note, /OpenSky Network/); assert.match(envelope.note, /4m ago/); assert.match(envelope.note, /do not describe this as live/i); }); test('degraded and unavailable envelopes outrank leftover nominal neighbours', () => { const layers = layerSnapshots( [ { id: 'earthquakes', name: 'Earthquakes', enabled: true, stats: { count: 3, lastUpdate: NOW - 10_000 }, }, { id: 'local-firms', name: 'Active Fires', enabled: true, stats: { error: 'FIRMS key missing', count: 8, lastUpdate: NOW - 30_000, }, }, ], { now: NOW }, ); const envelope = feedProvenanceEnvelope(layers, { now: NOW }); assert.equal(envelope.overall, 'degraded'); assert.match(envelope.note, /Active Fires is DEGRADED/); assert.match(envelope.note, /FIRMS key missing/); }); test('an unavailable feed with no prior data does not become a confident zero', () => { const snap = layerSnapshot( { id: 'ais-live-vessels', name: 'Live Vessels', enabled: true, stats: { error: 'AISStream down', count: 0, lastUpdate: null }, }, { now: NOW }, ); assert.equal(snap.feedState, 'unavailable'); const envelope = feedProvenanceEnvelope([snap]); assert.equal(envelope.overall, 'unavailable'); assert.match(envelope.note, /UNAVAILABLE/); }); test('fallback provenance names the fallback source instead of calling it live', () => { const snap = layerSnapshot( { id: 'flights', name: 'Live Flights', enabled: true, stats: { source: 'adsb.lol fallback', count: 40, lastUpdate: NOW - 8_000, }, }, { now: NOW }, ); assert.equal(snap.feedState, 'fallback'); assert.match(feedProvenanceNote([snap], 'fallback'), /adsb\.lol fallback/); }); test('view-state rows keep the existing identity fields and add feedState', () => { const row = viewStateLayerRow( { id: 'flights', name: 'Live Flights', enabled: true, stats: { count: 9, lastUpdate: 42, error: null, source: 'OpenSky Network', }, }, { now: NOW }, ); assert.deepEqual(row, { id: 'flights', name: 'Live Flights', enabled: true, count: 9, error: null, feedState: 'nominal', source: 'OpenSky Network', lastUpdate: 42, }); }); test('empty roster envelope does not pretend to be nominal', () => { const envelope = feedProvenanceEnvelope([]); assert.equal(envelope.overall, null); assert.match(envelope.note, /Do not invent/); }); test('age labels match the Data Layers meta clock', () => { assert.equal(snapshotAgeSec(null, NOW), null); assert.equal(snapshotAgeLabel(NOW - 3_000, NOW), 'just now'); assert.equal(snapshotAgeLabel(NOW - 12_000, NOW), '12s ago'); assert.equal(snapshotAgeLabel(NOW - 240_000, NOW), '4m ago'); assert.equal(snapshotAgeLabel(NOW - 7_200_000, NOW), '2h ago'); }); test('the warning tag names the layers in the reported state first', () => { const tag = provenanceTag({ overall: 'unavailable', layers: [ { name: 'Live Flights', feedState: 'partial' }, { name: 'Live AIS Vessels', feedState: 'partial' }, { name: 'CCTV', feedState: 'unavailable' }, ], }); assert.deepEqual(tag, { state: 'unavailable', names: ['CCTV', 'Live Flights'], }); });