mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
379 lines
12 KiB
JavaScript
379 lines
12 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
createFlightSource,
|
|
createMilitarySource,
|
|
createVesselSource,
|
|
normalizeReadsbAircraft,
|
|
normalizeVesselObservation,
|
|
openSkySnapshot,
|
|
readsbSnapshot,
|
|
} from './index.js';
|
|
|
|
const now = 1800000000000;
|
|
const aircraft = [
|
|
'AbC123',
|
|
'TEST',
|
|
'Example',
|
|
now / 1000 - 15,
|
|
now / 1000 - 2,
|
|
-97,
|
|
30,
|
|
1000,
|
|
false,
|
|
100,
|
|
250,
|
|
-2,
|
|
null,
|
|
1040,
|
|
];
|
|
const response = (payload, headers = {}, status = 200) =>
|
|
Response.json(payload, { headers, status });
|
|
|
|
test('civil observations retain identity, separate altitude datums and source epochs', () => {
|
|
const snapshot = openSkySnapshot(
|
|
{ time: now / 1000 - 130, states: [aircraft] },
|
|
{ now, source: 'Test source', coverage: 'regional' },
|
|
);
|
|
const row = snapshot.records[0];
|
|
assert.equal(row.id, 'abc123');
|
|
assert.equal(row.baroAltitudeM, 1000);
|
|
assert.equal(row.ellipsoidAltitudeM, 1040);
|
|
assert.equal(row.positionTimeMs, now - 15000);
|
|
assert.equal(row.contactTimeMs, now - 2000);
|
|
assert.equal(snapshot.ageMs, 130000);
|
|
assert.equal(snapshot.freshness, 'stale');
|
|
assert.equal(snapshot.coverage, 'regional');
|
|
const missing = openSkySnapshot(
|
|
{ states: [[...aircraft.slice(0, 3), null, null, ...aircraft.slice(5)]] },
|
|
{ now },
|
|
);
|
|
assert.equal(missing.observedAtMs, null);
|
|
assert.equal(missing.freshness, 'unknown');
|
|
assert.equal(missing.records[0].positionTimeMs, null);
|
|
});
|
|
|
|
test('mixed-invalid and duplicate admissions are explicitly incomplete; all-invalid rejects', () => {
|
|
const invalid = [...aircraft];
|
|
invalid[5] = 181;
|
|
const partial = openSkySnapshot({ states: [aircraft, invalid, aircraft] });
|
|
assert.equal(partial.records.length, 1);
|
|
assert.equal(partial.complete, false);
|
|
assert.equal(partial.rejectedCount, 2);
|
|
assert.throws(() => openSkySnapshot({ states: [invalid] }), {
|
|
code: 'malformed',
|
|
});
|
|
assert.throws(() => openSkySnapshot({}), { code: 'malformed' });
|
|
assert.equal(openSkySnapshot({ states: [] }).complete, true);
|
|
});
|
|
|
|
test('position-less OpenSky rows are seen, not missing; malformed rows stay incomplete', () => {
|
|
// OpenSky's spec allows null longitude/latitude; every live /states/all
|
|
// carries such rows.
|
|
const unpositioned = ['DEF456', ...aircraft.slice(1)];
|
|
unpositioned[5] = null;
|
|
unpositioned[6] = null;
|
|
const snapshot = openSkySnapshot({ states: [aircraft, unpositioned] });
|
|
assert.equal(snapshot.records.length, 1);
|
|
assert.equal(snapshot.complete, true);
|
|
assert.deepEqual([...snapshot.retainIds], ['def456']);
|
|
assert.equal(snapshot.rejectedCount, 1);
|
|
const malformed = [...aircraft];
|
|
malformed[5] = 181;
|
|
const garbled = openSkySnapshot({
|
|
states: [aircraft, unpositioned, malformed],
|
|
});
|
|
assert.equal(garbled.complete, false);
|
|
assert.deepEqual([...garbled.retainIds], ['def456']);
|
|
// No usable id, only half a position, a truncated row, or no row at all.
|
|
for (const row of [
|
|
[' ', ...unpositioned.slice(1)],
|
|
Object.assign([...unpositioned], { 5: -97 }),
|
|
['DEF456'],
|
|
null,
|
|
])
|
|
assert.equal(openSkySnapshot({ states: [aircraft, row] }).complete, false);
|
|
// Repeated position-less rows stay complete.
|
|
const repeated = openSkySnapshot({
|
|
states: [aircraft, unpositioned, unpositioned],
|
|
});
|
|
assert.equal(repeated.complete, true);
|
|
// A feed with no positions at all still cannot prove absence.
|
|
assert.throws(() => openSkySnapshot({ states: [unpositioned] }), {
|
|
code: 'malformed',
|
|
});
|
|
// Military readsb admission keeps its existing semantics.
|
|
const military = readsbSnapshot(
|
|
{ ac: [{ hex: 'abc123', lat: 30, lon: -97 }, { hex: 'def456' }] },
|
|
{ observedAtMs: null },
|
|
);
|
|
assert.equal(military.complete, false);
|
|
assert.equal(military.retainIds.size, 0);
|
|
});
|
|
|
|
test('readsb units and position/contact ages are normalized once against the source epoch', () => {
|
|
const row = normalizeReadsbAircraft(
|
|
{
|
|
hex: 'abc123',
|
|
lat: '30',
|
|
lon: '-97',
|
|
alt_baro: 10000,
|
|
alt_geom: 10100,
|
|
gs: 120,
|
|
baro_rate: -600,
|
|
seen: 2,
|
|
seen_pos: 12,
|
|
},
|
|
now,
|
|
);
|
|
assert.equal(row.baroAltitudeM, 3048);
|
|
assert.equal(row.ellipsoidAltitudeM, 3078.48);
|
|
assert.equal(row.speedMps, 120 * 0.514444);
|
|
assert.equal(row.verticalRateMps, -600 * 0.00508);
|
|
assert.equal(row.positionTimeMs, now - 12000);
|
|
assert.equal(row.contactTimeMs, now - 2000);
|
|
const ground = normalizeReadsbAircraft(
|
|
{ hex: 'abc123', lat: 30, lon: -97, alt_baro: 'ground' },
|
|
null,
|
|
);
|
|
assert.equal(ground.onGround, true);
|
|
assert.equal(ground.baroAltitudeM, null);
|
|
assert.equal(ground.positionTimeMs, null);
|
|
assert.equal(
|
|
readsbSnapshot({ ac: [] }, { observedAtMs: null }).freshness,
|
|
'unknown',
|
|
);
|
|
});
|
|
|
|
test('vessel identity, opaque reference and heading/course remain distinct, unknowns stay null', () => {
|
|
const row = normalizeVesselObservation(
|
|
{
|
|
mmsi: '123456789',
|
|
lat: 30,
|
|
lon: -97,
|
|
speed: 10,
|
|
heading: 45,
|
|
course: 60,
|
|
last_position_epoch: now / 1000,
|
|
extra: 'discard',
|
|
},
|
|
'opaque-reference',
|
|
);
|
|
assert.equal(row.id, '123456789');
|
|
assert.equal(row.reference, 'opaque-reference');
|
|
assert.equal(row.headingDeg, 45);
|
|
assert.equal(row.courseDeg, 60);
|
|
assert.equal(row.speedMps, 10 * 0.514444);
|
|
assert.equal(row.altitudeDatum, 'sea-surface');
|
|
assert.equal(row.observedAtMs, now);
|
|
assert.equal('extra' in row, false);
|
|
assert.equal(
|
|
normalizeVesselObservation({ mmsi: 'x', lat: null, lon: null }),
|
|
null,
|
|
);
|
|
});
|
|
|
|
test('construction is inert; adapters preserve routes, viewport query, cache epoch and history datum', async () => {
|
|
const requests = [];
|
|
const fetchImpl = async (url, init) => {
|
|
requests.push({ url, init });
|
|
if (url.startsWith('/api/flights?'))
|
|
return response(
|
|
{ time: now / 1000, states: [aircraft] },
|
|
{
|
|
'x-flight-source': 'adsb.lol',
|
|
'x-flight-coverage': '250 nm regional',
|
|
},
|
|
);
|
|
if (url.startsWith('/api/flights/track'))
|
|
return response({ path: [[now / 1000 - 30, 30, -97, 1000, 45, false]] });
|
|
if (url === '/api/military')
|
|
return response(
|
|
{ ac: [{ hex: 'abc123', lat: 30, lon: -97, seen_pos: 15 }] },
|
|
{ 'x-feed-age-ms': '60000', 'x-feed-cache': 'STALE' },
|
|
);
|
|
return response({
|
|
timestamp: now / 1000 - 60,
|
|
trace: [[10, 30, -97, 1000]],
|
|
});
|
|
};
|
|
const civil = createFlightSource({ fetchImpl, now: () => now });
|
|
const military = createMilitarySource({ fetchImpl, now: () => now });
|
|
assert.equal(requests.length, 0);
|
|
const signal = new AbortController().signal;
|
|
const snapshot = await civil.getSnapshot(
|
|
{ latitude: 30.123456, longitude: -97 },
|
|
{ signal },
|
|
);
|
|
assert.equal(requests[0].url, '/api/flights?lat=30.1235&lon=-97.0000');
|
|
assert.equal(requests[0].init.signal, signal);
|
|
assert.equal(snapshot.source, 'adsb.lol');
|
|
assert.equal(snapshot.coverage, '250 nm regional');
|
|
assert.equal((await civil.getTrack('abc123')).records[0].baroAltitudeM, 1000);
|
|
const mil = await military.getSnapshot();
|
|
assert.equal(mil.observedAtMs, now - 60000);
|
|
assert.equal(mil.records[0].positionTimeMs, now - 75000);
|
|
assert.equal(mil.stale, true);
|
|
assert.equal(military.getTrack, undefined);
|
|
assert.ok(
|
|
requests.every(({ url }) => !url.startsWith('/api/military/track')),
|
|
);
|
|
});
|
|
|
|
test('cancellation after slow body parsing rejects even with a transport that ignores abort', async () => {
|
|
let release;
|
|
const controller = new AbortController();
|
|
const source = createFlightSource({
|
|
fetchImpl: async () => ({
|
|
ok: true,
|
|
json: () =>
|
|
new Promise((resolve) => {
|
|
release = resolve;
|
|
}),
|
|
}),
|
|
});
|
|
const pending = source.getSnapshot({}, { signal: controller.signal });
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
controller.abort();
|
|
release({ states: [aircraft] });
|
|
await assert.rejects(pending, { name: 'AbortError' });
|
|
});
|
|
|
|
test('denials and outages never start another source and do not echo arbitrary response bodies', async () => {
|
|
for (const status of [401, 403, 429, 502]) {
|
|
let calls = 0;
|
|
const source = createFlightSource({
|
|
fetchImpl: async () => {
|
|
calls++;
|
|
return response({ error: 'sensitive upstream detail' }, {}, status);
|
|
},
|
|
});
|
|
await assert.rejects(source.getSnapshot(), (error) => {
|
|
assert.equal(error.status, status);
|
|
assert.equal(error.message.includes('sensitive'), false);
|
|
assert.ok(error.retryAfterMs >= 20000);
|
|
return true;
|
|
});
|
|
assert.equal(calls, 1);
|
|
}
|
|
});
|
|
|
|
test('AIS reports limited received coverage and keeps connection state separate from positions', async () => {
|
|
const source = createVesselSource({
|
|
origin: () => 'http://example.test',
|
|
fetchImpl: async (url) => {
|
|
if (url.includes('/track?'))
|
|
return response({ samples: [{ lat: 30, lon: -97, t: now / 1000 }] });
|
|
assert.equal(url, 'http://example.test/api/vessels?maxRows=500');
|
|
return response({
|
|
status: 'reconnecting',
|
|
refreshing: true,
|
|
rows: [
|
|
{
|
|
mmsi: '123456789',
|
|
lat: 30,
|
|
lon: -97,
|
|
last_position_UTC: new Date(now).toISOString(),
|
|
},
|
|
],
|
|
newestPositionAt: new Date(now).toISOString(),
|
|
});
|
|
},
|
|
});
|
|
const snapshot = await source.getSnapshot({ maxRows: 500 });
|
|
assert.equal(snapshot.complete, false);
|
|
assert.equal(snapshot.stale, true);
|
|
assert.equal(snapshot.observedAtMs, now);
|
|
assert.equal(snapshot.transportStatus, 'reconnecting');
|
|
assert.equal(
|
|
(await source.getTrack('123456789')).records[0].observedAtMs,
|
|
now,
|
|
);
|
|
});
|
|
|
|
test('a malformed vessel row cannot prevent admission of valid positions', async () => {
|
|
const source = createVesselSource({
|
|
fetchImpl: async () =>
|
|
response({
|
|
status: 'live',
|
|
rows: [null, { mmsi: '123456789', lat: 30, lon: -97 }],
|
|
}),
|
|
});
|
|
const snapshot = await source.getSnapshot();
|
|
assert.equal(snapshot.records.length, 1);
|
|
assert.equal(snapshot.rejectedCount, 1);
|
|
assert.equal(snapshot.complete, false);
|
|
});
|
|
|
|
test('out-of-range source epochs do not reach Date or globe time constructors', () => {
|
|
const row = [...aircraft];
|
|
row[3] = 1e100;
|
|
row[4] = 1e100;
|
|
const snapshot = openSkySnapshot({ time: 1e100, states: [row] });
|
|
assert.equal(snapshot.observedAtMs, null);
|
|
assert.equal(snapshot.records[0].positionTimeMs, null);
|
|
assert.equal(snapshot.records[0].contactTimeMs, null);
|
|
});
|
|
|
|
test('classification identities include positionless aircraft without admitting them to rendering', async () => {
|
|
const source = createMilitarySource({
|
|
fetchImpl: async () =>
|
|
response({
|
|
ac: [
|
|
{ hex: ' ABC123 ' },
|
|
{ hex: 'abc123' },
|
|
{ hex: 'def456', lat: 30, lon: -97 },
|
|
],
|
|
}),
|
|
});
|
|
assert.deepEqual(await source.getIdentities(), ['abc123', 'def456']);
|
|
const snapshot = await source.getSnapshot();
|
|
assert.deepEqual(
|
|
snapshot.records.map((record) => record.id),
|
|
['def456'],
|
|
);
|
|
assert.equal(snapshot.complete, false);
|
|
});
|
|
|
|
test('identity lookup validates its response and honors body-parse cancellation', async () => {
|
|
const malformed = createMilitarySource({
|
|
fetchImpl: async () => response({ ac: [{}] }),
|
|
});
|
|
await assert.rejects(malformed.getIdentities(), /Malformed/);
|
|
const abort = new AbortController();
|
|
const source = createMilitarySource({
|
|
fetchImpl: async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
headers: new Headers(),
|
|
async json() {
|
|
abort.abort();
|
|
return { ac: [{ hex: 'abc123' }] };
|
|
},
|
|
}),
|
|
});
|
|
await assert.rejects(source.getIdentities({}, { signal: abort.signal }), {
|
|
name: 'AbortError',
|
|
});
|
|
});
|
|
|
|
test('squawks are validated and omitted OpenSky status differs from an explicit ADS-B clear', () => {
|
|
const squawking = [...aircraft, '7700'];
|
|
const civil = openSkySnapshot({ states: [squawking] }, { now }).records[0];
|
|
assert.equal(civil.squawk, '7700');
|
|
assert.equal(civil.emergency, undefined);
|
|
assert.equal(openSkySnapshot({ states: [aircraft] }).records[0].squawk, null);
|
|
const mil = normalizeReadsbAircraft(
|
|
{ hex: 'abc123', lat: 30, lon: -97, squawk: '7600', emergency: 'NORDO' },
|
|
now,
|
|
);
|
|
assert.equal(mil.squawk, '7600');
|
|
assert.equal(mil.emergency, 'nordo');
|
|
const quiet = normalizeReadsbAircraft(
|
|
{ hex: 'abc123', lat: 30, lon: -97 },
|
|
now,
|
|
);
|
|
assert.equal(quiet.squawk, null);
|
|
assert.equal(quiet.emergency, null);
|
|
});
|