// src/data/firmsCsv.test.mjs import { test } from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { acquisitionMsUtc, filterTrailing24h, isLikelyCsv, parseFirmsCsv, } from 'gods-eye-view/sources/firms-csv'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const FIXTURE = fs.readFileSync( path.join(__dirname, 'fixtures', 'firms-viirs-noaa20-sample.csv'), 'utf8', ); const HEADER = 'latitude,longitude,bright_ti4,scan,track,acq_date,acq_time,satellite,instrument,confidence,version,bright_ti5,frp,daynight'; test('fixture parses to 45 records with finite lat/lon/frp', () => { const records = parseFirmsCsv(FIXTURE); assert.ok(Array.isArray(records)); assert.equal(records.length, 45); for (const record of records) { assert.ok(Number.isFinite(record.lat), `lat finite: ${record.lat}`); assert.ok(Number.isFinite(record.lon), `lon finite: ${record.lon}`); assert.ok(Number.isFinite(record.frp), `frp finite: ${record.frp}`); assert.ok(record.lat >= -90 && record.lat <= 90); assert.ok(record.lon >= -180 && record.lon <= 180); } }); test('fixture first row maps every field, categorical confidence preserved raw', () => { const first = parseFirmsCsv(FIXTURE)[0]; assert.equal(first.lat, 38.99488); assert.equal(first.lon, -121.67046); assert.equal(first.brightness, 303.6); // bright_ti4 assert.equal(first.brightnessTi5, 290.73); // bright_ti5 assert.equal(first.frp, 0.53); assert.equal(first.confidence, 'n'); // categorical, passed through untouched assert.equal(first.daynight, 'N'); assert.equal(first.acqDate, '2026-07-16'); assert.equal(first.acqTime, '1006'); // string, as-is assert.equal(first.satellite, 'N20'); assert.equal(first.instrument, 'VIIRS'); }); test('MODIS schema maps fields and preserves numeric confidence raw', () => { const header = 'latitude,longitude,brightness,scan,track,acq_date,acq_time,satellite,instrument,confidence,version,bright_t31,frp,daynight'; const row = '34.123,-117.456,321.5,1.2,0.8,2026-07-16,1045,Aqua,MODIS,78,6.1NRT,290.25,12.4,D'; const [record] = parseFirmsCsv(`${header}\n${row}\n`); assert.equal(record.lat, 34.123); assert.equal(record.lon, -117.456); assert.equal(record.frp, 12.4); assert.equal(record.brightness, 321.5); assert.equal(record.brightnessTi5, 290.25); assert.equal(record.daynight, 'D'); assert.equal(record.acqDate, '2026-07-16'); assert.equal(record.acqTime, '1045'); assert.equal(record.satellite, 'Aqua'); assert.equal(record.instrument, 'MODIS'); assert.equal(record.confidence, '78'); }); test('acquisitionMsUtc: "1006" = 10:06 UTC', () => { assert.equal( acquisitionMsUtc('2026-07-16', '1006'), Date.UTC(2026, 6, 16, 10, 6), ); }); test('acquisitionMsUtc: non-zero-padded "45" = 00:45 UTC', () => { assert.equal( acquisitionMsUtc('2026-07-16', '45'), Date.UTC(2026, 6, 16, 0, 45), ); }); test('acquisitionMsUtc: "0" = midnight UTC; garbage → NaN', () => { assert.equal( acquisitionMsUtc('2026-07-16', '0'), Date.UTC(2026, 6, 16, 0, 0), ); assert.ok(Number.isNaN(acquisitionMsUtc('not-a-date', '1006'))); assert.ok(Number.isNaN(acquisitionMsUtc('2026-07-16', 'xx'))); }); test('header-only input → []', () => { assert.deepEqual(parseFirmsCsv(`${HEADER}\n`), []); assert.deepEqual(parseFirmsCsv(HEADER), []); }); test('CRLF line endings and trailing newline tolerated', () => { const text = `${HEADER}\r\n38.99488,-121.67046,303.6,0.39,0.36,2026-07-16,1006,N20,VIIRS,n,2.0NRT,290.73,0.53,N\r\n\r\n`; const records = parseFirmsCsv(text); assert.equal(records.length, 1); assert.equal(records[0].lat, 38.99488); assert.equal(records[0].daynight, 'N'); // no trailing \r leaking into the last field }); test('malformed rows are skipped, valid rows kept', () => { const text = [ HEADER, 'not,enough,fields', 'garbage-line-with-no-commas', 'NaN,-121.67046,303.6,0.39,0.36,2026-07-16,1006,N20,VIIRS,n,2.0NRT,290.73,0.53,N', '38.99488,-121.67046,303.6,0.39,0.36,2026-07-16,1006,N20,VIIRS,n,2.0NRT,290.73,0.53,N', ].join('\n'); const records = parseFirmsCsv(text); assert.equal(records.length, 1); assert.equal(records[0].lat, 38.99488); }); test('HTML error page detected as not-CSV', () => { const html = '\n