Files
gods-eye-view/src/data/firmsProxy.test.mjs
T

81 lines
3.8 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import test from 'node:test';
import { filterTrailing24h, parseFirmsCsv } from './firmsCsv.js';
const config = fs.readFileSync(new URL('../../server/providers/firms.js', import.meta.url), 'utf8');
const start = config.indexOf(' async function refreshUpstream(key) {');
assert.notEqual(start, -1, 'FIRMS refresh function exists');
const end = config.indexOf('\n }', start);
assert.notEqual(end, -1, 'FIRMS refresh function closes');
const refreshSource = config.slice(start, end + 4);
const SOURCES = ['VIIRS_NOAA20_NRT', 'VIIRS_NOAA21_NRT', 'VIIRS_SNPP_NRT'];
const NOW = Date.UTC(2026, 8, 11, 12);
const recent = { acqDate: '2026-09-11', acqTime: '1100' };
// Exercise the production refresh without opening a server or using a MAP_KEY.
// Inject only its upstream, clock and filter dependencies; keep its aggregation
// and source-status code intact, including failures while consuming records.
function createRefresh(fetchSource, filter = filterTrailing24h) {
return new Function('SOURCES', 'fetchSource', 'filterTrailing24h', 'Date', 'console',
`return (${refreshSource});`)(SOURCES, fetchSource, filter, { now: () => NOW }, { warn() {} });
}
test('FIRMS retains large sources in order and filters expired rows', async () => {
const header = 'latitude,longitude,acq_date,acq_time,confidence,frp\n';
const large = parseFirmsCsv(header + '1,2,2026-09-11,1100,n,4\n'.repeat(200_000));
const expired = { ...recent, acqDate: '2026-09-09' };
const last = { ...recent, marker: 'last' };
const calls = [];
let active = 0;
const refresh = createRefresh(async (key, source) => {
assert.equal(active++, 0, 'sources must be fetched sequentially');
calls.push(source);
await Promise.resolve();
active--;
return source === SOURCES[0] ? [...large, expired] : source === SOURCES[1] ? [last] : [];
});
const result = await refresh('fixture');
assert.deepEqual(calls, SOURCES);
assert.equal(result.fires.length, 200_001);
assert.equal(result.fires[0], large[0]);
assert.equal(result.fires[199_999], large.at(-1));
assert.equal(result.fires.at(-1), last);
assert.deepEqual(result.sources, SOURCES.map((source, index) => ({
source, count: [200_000, 1, 0][index], ok: true,
})));
});
test('FIRMS keeps successful sources when another upstream fails', async () => {
const result = await createRefresh(async (key, source) => {
if (source === SOURCES[1]) throw new Error('upstream unavailable');
return [recent];
})('fixture');
assert.equal(result.fires.length, 2);
assert.deepEqual(result.sources, SOURCES.map((source, index) => ({
source, count: index === 1 ? 0 : 1, ok: index !== 1,
})));
});
test('FIRMS reports one failure if consuming a source throws before append', async () => {
const failing = [];
const result = await createRefresh(async (key, source) => source === SOURCES[0] ? failing : [recent],
(records, now) => {
if (records !== failing) return filterTrailing24h(records, now);
// Fault injection for aggregation; ordinary parsed CSV returns an array.
return { length: 1, [Symbol.iterator]() { throw new Error('aggregation failed'); } };
})('fixture');
assert.equal(result.fires.length, 2);
assert.deepEqual(result.sources, SOURCES.map((source, index) => ({
source, count: index === 0 ? 0 : 1, ok: index !== 0,
})));
});
test('FIRMS distinguishes all-source failure from successful empty sources', async () => {
await assert.rejects(createRefresh(async () => { throw new Error('upstream unavailable'); })('fixture'),
/all FIRMS sources failed/);
const result = await createRefresh(async () => [])('fixture');
assert.deepEqual(result.fires, []);
assert.deepEqual(result.sources, SOURCES.map(source => ({ source, count: 0, ok: true })));
});