mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
fix: integrate GNSS and emergency features with current main contracts
This commit is contained in:
1 parent
69e8b18e02
commit
ec80c89c1e
27 files changed
+1581
-453
No files matched your search
@@ -94,8 +94,25 @@ export function normalizeAdsbLolPointResponse(payload) {
|
||||
: Math.floor(
|
||||
responseNow > 10_000_000_000 ? responseNow / 1000 : responseNow,
|
||||
);
|
||||
const states = (Array.isArray(payload?.ac) ? payload.ac : [])
|
||||
const aircraft = Array.isArray(payload?.ac) ? payload.ac : [];
|
||||
const states = aircraft
|
||||
.map((aircraft) => normalizeAdsbLolAircraftState(aircraft, nowSeconds))
|
||||
.filter(Boolean);
|
||||
return { time: nowSeconds, states };
|
||||
// Keep the OpenSky vectors unchanged. The regional fallback's extra readsb
|
||||
// status travels alongside them, keyed only to admitted aircraft.
|
||||
const admitted = new Set(states.map((row) => row[0]));
|
||||
const emergencyByIcao = Object.fromEntries(
|
||||
aircraft.flatMap((row) => {
|
||||
const id = String(row?.hex || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!admitted.has(id) || typeof row?.emergency !== 'string') return [];
|
||||
return [[id, row.emergency.trim().toLowerCase()]];
|
||||
}),
|
||||
);
|
||||
return {
|
||||
time: nowSeconds,
|
||||
states,
|
||||
...(Object.keys(emergencyByIcao).length ? { emergencyByIcao } : {}),
|
||||
};
|
||||
}
|
||||
@@ -11,9 +11,157 @@ import { MilitaryFlightRecords } from '../layers/military/records.js';
|
||||
import { mapAnalystRecord as mapFlight } from './flights.js';
|
||||
import { mapAnalystRecord as mapMilitary } from './militaryFlights.js';
|
||||
import { createAnalystEngine } from './analystEngine.js';
|
||||
import { normalizeAdsbLolPointResponse } from './adsbLolFallback.js';
|
||||
import { createGevActionRunner } from '../voice/gevActions.js';
|
||||
|
||||
const NOW_MS = 1800000000000;
|
||||
|
||||
function civilStore() {
|
||||
return new FlightRecords({
|
||||
geoidHeight: () => 0,
|
||||
cachedGroundFloor: () => null,
|
||||
floorAltitudeM: (value) => value,
|
||||
approxDistanceKm: () => 0,
|
||||
});
|
||||
}
|
||||
const receiveContext = {
|
||||
viewerLatDeg: null,
|
||||
viewerLonDeg: null,
|
||||
trackedId: null,
|
||||
floorWarmPoints: [],
|
||||
};
|
||||
|
||||
test('real civil fallback preserves ADS-B emergency and priority, then clears', () => {
|
||||
const store = civilStore();
|
||||
const aircraft = {
|
||||
hex: 'a1b2c3',
|
||||
flight: 'TEST1',
|
||||
lat: 30,
|
||||
lon: -97,
|
||||
alt_baro: 10000,
|
||||
gs: 150,
|
||||
track: 90,
|
||||
squawk: '1200',
|
||||
seen: 3,
|
||||
seen_pos: 1,
|
||||
};
|
||||
for (const [status, expectedKind, flagged] of [
|
||||
['general', 'general', true],
|
||||
['minfuel', 'minfuel', false],
|
||||
['none', null, false],
|
||||
]) {
|
||||
const payload = normalizeAdsbLolPointResponse({
|
||||
now: NOW_MS,
|
||||
ac: [{ ...aircraft, emergency: status }],
|
||||
});
|
||||
assert.equal(
|
||||
payload.states[0].length,
|
||||
18,
|
||||
'fallback vectors retain the OpenSky shape',
|
||||
);
|
||||
const [observation] = openSkySnapshot(payload, { now: NOW_MS }).records;
|
||||
const result = mapFlight(
|
||||
aircraft.hex,
|
||||
store.receive(observation, receiveContext).meta,
|
||||
);
|
||||
assert.equal(result.emergency, flagged);
|
||||
assert.equal(result.emergencyKind, expectedKind);
|
||||
assert.equal(result.emergencySource, expectedKind ? 'ads-b' : null);
|
||||
assert.equal(result.lastContactAtMs, NOW_MS - 3000);
|
||||
}
|
||||
});
|
||||
|
||||
test('changed status without contact time retains general contact, including voice projection', async () => {
|
||||
const store = civilStore();
|
||||
const [observation] = readsbSnapshot(
|
||||
{
|
||||
ac: [
|
||||
{
|
||||
hex: 'a1b2c3',
|
||||
flight: 'TEST1',
|
||||
lat: 30,
|
||||
lon: -97,
|
||||
alt_baro: 10000,
|
||||
gs: 150,
|
||||
track: 90,
|
||||
squawk: '1200',
|
||||
emergency: 'general',
|
||||
seen: 3,
|
||||
seen_pos: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ observedAtMs: NOW_MS, now: NOW_MS },
|
||||
).records;
|
||||
store.receive(observation, receiveContext);
|
||||
const meta = store.receive(
|
||||
{ ...observation, contactTimeMs: null, squawk: '7600', emergency: 'none' },
|
||||
receiveContext,
|
||||
).meta;
|
||||
const record = mapFlight(observation.id, meta);
|
||||
assert.equal(record.emergencyKind, 'nordo');
|
||||
assert.equal(record.lastContactAtMs, NOW_MS - 3000);
|
||||
assert.equal(Object.hasOwn(record, 'observedAtMs'), false);
|
||||
globalThis.window = globalThis.window || {
|
||||
clearTimeout,
|
||||
setTimeout,
|
||||
requestIdleCallback: null,
|
||||
};
|
||||
const flights = {
|
||||
id: 'flights',
|
||||
source: 'fixture',
|
||||
getStats: () => ({ count: 1 }),
|
||||
getAnalystRecords: () => [record],
|
||||
};
|
||||
const runner = createGevActionRunner({
|
||||
viewer: {
|
||||
clock: { onTick: { addEventListener: () => () => {} } },
|
||||
scene: { canvas: { addEventListener() {}, removeEventListener() {} } },
|
||||
camera: {
|
||||
moveEnd: { addEventListener() {} },
|
||||
positionCartographic: {
|
||||
height: 300000,
|
||||
latitude: 0.52,
|
||||
longitude: -1.71,
|
||||
},
|
||||
},
|
||||
},
|
||||
styleManager: {
|
||||
getContextModeState: () => ({ mode: null, active: false }),
|
||||
getCockpitState: () => ({ active: false }),
|
||||
},
|
||||
dataManager: {
|
||||
layers: new Map([['flights', { module: flights }]]),
|
||||
isEnabled: (id) => id === 'flights',
|
||||
getAll: () => [
|
||||
{ id: 'flights', name: 'Flights', enabled: true, stats: { count: 1 } },
|
||||
],
|
||||
},
|
||||
});
|
||||
try {
|
||||
const result = await runner('analyst_query', {
|
||||
layers: ['flights'],
|
||||
scope: { kind: 'anywhere' },
|
||||
filters: [{ field: 'emergency', op: 'eq', value: true }],
|
||||
});
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.items[0].emergency, true);
|
||||
assert.equal(result.items[0].lastContactAtMs, NOW_MS - 3000);
|
||||
assert.equal(result.items[0].emergencySource, 'squawk');
|
||||
assert.equal(
|
||||
result.items[0].emergencyBroadcast,
|
||||
'broadcasting squawk 7600 (radio failure)',
|
||||
);
|
||||
assert.equal(
|
||||
Object.hasOwn(result.items[0], 'time'),
|
||||
false,
|
||||
'general contact is not a generic incident timestamp',
|
||||
);
|
||||
} finally {
|
||||
runner.dispose?.();
|
||||
}
|
||||
});
|
||||
|
||||
// OpenSky /states/all: [icao24, callsign, origin_country, time_position,
|
||||
// last_contact, lon, lat, baro_alt, on_ground, velocity, true_track,
|
||||
// vertical_rate, sensors, geo_alt, squawk, spi, position_source, category].
|
||||
@@ -110,14 +258,14 @@ test('OpenSky 7700 and adsb.lol emergency=general differ only in provenance', ()
|
||||
civil.emergencyBroadcast,
|
||||
'broadcasting squawk 7700 (general emergency)',
|
||||
);
|
||||
assert.equal(civil.observedAtMs, NOW_MS - 3000, 'OpenSky last_contact');
|
||||
assert.equal(civil.lastContactAtMs, NOW_MS - 3000, 'OpenSky last_contact');
|
||||
|
||||
assert.equal(military.emergency, true);
|
||||
assert.equal(military.emergencyKind, 'general');
|
||||
assert.equal(military.squawk, '1200', 'the code is not an emergency code');
|
||||
assert.equal(military.emergencySource, 'ads-b');
|
||||
assert.equal(military.emergencyBroadcast, 'ADS-B reports general emergency');
|
||||
assert.equal(military.observedAtMs, NOW_MS - 1500, 'snapshot time - seen');
|
||||
assert.equal(military.lastContactAtMs, NOW_MS - 1500, 'snapshot time - seen');
|
||||
});
|
||||
|
||||
test('the analyst can filter emergencies by source', async () => {
|
||||
@@ -150,3 +298,40 @@ test('the analyst can filter emergencies by source', async () => {
|
||||
[['ae01ce', 'ADS-B reports general emergency']],
|
||||
);
|
||||
});
|
||||
|
||||
test('military status changes retain only the latest known general contact', () => {
|
||||
const store = new MilitaryFlightRecords({
|
||||
geoidHeight: () => 0,
|
||||
cachedGroundFloor: () => null,
|
||||
floorAltitudeM: (v) => v,
|
||||
});
|
||||
const [observation] = readsbSnapshot(ADSB_LOL, {
|
||||
observedAtMs: NOW_MS,
|
||||
now: NOW_MS,
|
||||
}).records;
|
||||
const context = {
|
||||
observedAtMs: NOW_MS,
|
||||
floorWarmPoints: [],
|
||||
modelOwnsVisual: false,
|
||||
};
|
||||
const unknown = mapMilitary(
|
||||
observation.id,
|
||||
store.receive({ ...observation, contactTimeMs: null }, context).meta,
|
||||
);
|
||||
assert.equal(unknown.lastContactAtMs, null);
|
||||
store.receive(observation, context);
|
||||
const changed = mapMilitary(
|
||||
observation.id,
|
||||
store.receive(
|
||||
{
|
||||
...observation,
|
||||
contactTimeMs: null,
|
||||
squawk: '7600',
|
||||
emergency: 'none',
|
||||
},
|
||||
context,
|
||||
).meta,
|
||||
);
|
||||
assert.equal(changed.emergencyKind, 'nordo');
|
||||
assert.equal(changed.lastContactAtMs, NOW_MS - 1500);
|
||||
});
|
||||
@@ -7,7 +7,7 @@ const TEXLAND = { name: 'Texland', ring: [[-100, 28], [-94, 28], [-94, 33], [-10
|
||||
const FLIGHTS = [
|
||||
{ id: 'SWA1', lat: 30.2, lon: -97.7, altitudeM: 11000, speedMps: 240, military: false, onGround: false, routeOrigin: 'AUS', routeDestination: 'LAX' },
|
||||
{ id: 'RCH01', lat: 31.0, lon: -97.0, altitudeM: 13500, speedMps: 250, military: true, onGround: false, routeOrigin: null, routeDestination: null },
|
||||
{ id: 'N123', lat: 45.0, lon: -122.0, altitudeM: 2000, speedMps: 80, military: false, onGround: false, squawk: '7700', emergency: true, emergencyKind: 'general', routeOrigin: null, routeDestination: null },
|
||||
{ id: 'N123', lat: 45.0, lon: -122.0, altitudeM: 2000, speedMps: 80, military: false, onGround: false, routeOrigin: null, routeDestination: null },
|
||||
{ id: 'GND1', lat: 30.19, lon: -97.66, altitudeM: 150, speedMps: 5, military: false, onGround: true, routeOrigin: null, routeDestination: null },
|
||||
];
|
||||
const SHIPS = [
|
||||
@@ -325,19 +325,6 @@ test('analyst: route fields queryable from cached enrichment only', async () =>
|
||||
assert.deepEqual(r.items.map((i) => i.id), ['SWA1'], 'null route fields never match');
|
||||
});
|
||||
|
||||
test('analyst: aircraft broadcasting an emergency are filterable by flag and code', async () => {
|
||||
const flagged = await makeEngine().query({
|
||||
layers: ['flights'], scope: { kind: 'anywhere' },
|
||||
filters: [{ field: 'emergency', op: 'eq', value: true }],
|
||||
});
|
||||
assert.deepEqual(flagged.items.map((i) => i.id), ['N123']);
|
||||
const coded = await makeEngine().query({
|
||||
layers: ['flights'], scope: { kind: 'anywhere' },
|
||||
filters: [{ field: 'squawk', op: 'eq', value: '7700' }],
|
||||
});
|
||||
assert.deepEqual(coded.items.map((i) => i.id), ['N123']);
|
||||
});
|
||||
|
||||
test('analyst: satellites and local infrastructure are queryable layers', async () => {
|
||||
const SATS = [
|
||||
{ id: 'ISS (ZARYA)', noradId: '25544', name: 'ISS (ZARYA)', lat: 30.3, lon: -97.7, altitudeM: 410000, satelliteClass: 'STATION · ISS', group: 'stations' },
|
||||
@@ -519,7 +506,6 @@ test('time fields filter by ISO and derive ageHours; the feed window is exposed'
|
||||
assert.equal(age.coverage.layersQueried[0].window, 'last 24 h');
|
||||
});
|
||||
|
||||
|
||||
test('follow-up provenance stays attached to old rows after a feed recovers', async () => {
|
||||
let state = 'stale';
|
||||
const engine = createAnalystEngine({
|
||||
|
||||
+880
-237
File diff suppressed because it is too large.
Load diff
@@ -205,11 +205,16 @@ test('an upstream 429 starts a cooldown with no further upstream calls', async (
|
||||
assert.equal(calls, 2);
|
||||
});
|
||||
|
||||
test('Retry-After accepts seconds or an HTTP date, clamped to 5 s – 120 s', () => {
|
||||
test('Retry-After accepts seconds or an HTTP date, with a 5 s minimum and no truncated deadline', () => {
|
||||
const nowMs = Date.parse('2026-09-29T12:00:00Z');
|
||||
assert.equal(gnssRetryCooldownMs('30', nowMs), 30_000);
|
||||
assert.equal(gnssRetryCooldownMs('1', nowMs), 5_000);
|
||||
assert.equal(gnssRetryCooldownMs('3600', nowMs), 120_000);
|
||||
assert.equal(gnssRetryCooldownMs('3600', nowMs), 3_600_000);
|
||||
assert.equal(
|
||||
gnssRetryCooldownMs('Tue, 29 Sep 2026 13:00:00 GMT', nowMs),
|
||||
3_600_000,
|
||||
);
|
||||
assert.equal(gnssRetryCooldownMs('1e309', nowMs), 60_000);
|
||||
assert.equal(
|
||||
gnssRetryCooldownMs('Tue, 29 Sep 2026 12:00:45 GMT', nowMs),
|
||||
45_000,
|
||||
@@ -223,6 +228,40 @@ test('Retry-After accepts seconds or an HTTP date, clamped to 5 s – 120 s', ()
|
||||
assert.equal(gnssRetryCooldownMs(null, nowMs), 60_000);
|
||||
});
|
||||
|
||||
test('overlapping 429s preserve the longest shared deadline', async () => {
|
||||
let clock = 0,
|
||||
calls = 0;
|
||||
const pending = [];
|
||||
const request = install({
|
||||
now: () => clock,
|
||||
fetchImpl: () => {
|
||||
calls += 1;
|
||||
return new Promise((resolve) => pending.push(resolve));
|
||||
},
|
||||
});
|
||||
const long = request('/?lat=1&lon=0'),
|
||||
short = request('/?lat=2&lon=0');
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
pending[0](
|
||||
new Response('', { status: 429, headers: { 'Retry-After': '3600' } }),
|
||||
);
|
||||
assert.equal((await long).headers['Retry-After'], '3600');
|
||||
clock = 1000;
|
||||
pending[1](
|
||||
new Response('', { status: 429, headers: { 'Retry-After': '5' } }),
|
||||
);
|
||||
assert.equal((await short).headers['Retry-After'], '3599');
|
||||
clock = 7000;
|
||||
assert.equal((await request('/?lat=3&lon=0')).status, 429);
|
||||
assert.equal(calls, 2);
|
||||
clock = 3_600_001;
|
||||
const resumed = request('/?lat=3&lon=0');
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
pending[2](Response.json(snapshot));
|
||||
assert.equal((await resumed).status, 200);
|
||||
assert.equal(calls, 3);
|
||||
});
|
||||
|
||||
test('a cached anchor keeps serving its last snapshot as stale during a cooldown', async () => {
|
||||
let clock = 0;
|
||||
let limited = false;
|
||||
|
||||
@@ -609,7 +609,7 @@ export const LAYER_STATE_REGISTRY = Object.freeze([
|
||||
}),
|
||||
Object.freeze({
|
||||
id: 'gnss-interference',
|
||||
token: '0',
|
||||
token: '3',
|
||||
disposition: 'enabled-only',
|
||||
}),
|
||||
Object.freeze({ id: 'local-dams', token: 'q', disposition: 'enabled-only' }),
|
||||
|
||||
@@ -202,8 +202,8 @@ function encode(state) {
|
||||
|
||||
test('production registry is exact, canonical, and rejects incomplete contracts', async () => {
|
||||
assert.equal(validateLayerStateRegistry(), true);
|
||||
assert.equal(REGISTERED_LAYER_IDS.length, 29);
|
||||
assert.equal(new Set(REGISTERED_LAYER_IDS).size, 29);
|
||||
assert.equal(REGISTERED_LAYER_IDS.length, 30);
|
||||
assert.equal(new Set(REGISTERED_LAYER_IDS).size, 30);
|
||||
assert.ok(REGISTERED_LAYER_IDS.includes('transit'));
|
||||
assert.deepEqual(REGISTERED_LAYER_IDS, [...REGISTERED_LAYER_IDS].sort());
|
||||
assert.deepEqual(LEGACY_LAYER_STATE_TOKENS, {
|
||||
@@ -242,19 +242,19 @@ test('production registry is exact, canonical, and rejects incomplete contracts'
|
||||
for (const [id, token] of Object.entries(LEGACY_LAYER_STATE_TOKENS)) {
|
||||
assert.equal(LAYER_STATE_TOKEN_RESERVATIONS[id], token);
|
||||
}
|
||||
assert.equal(nextLayerStateToken(), '3');
|
||||
assert.equal(nextLayerStateToken(), '4');
|
||||
assert.equal(
|
||||
nextLayerStateToken({
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
alpha: '3',
|
||||
bravo: '4',
|
||||
alpha: '4',
|
||||
bravo: '5',
|
||||
}),
|
||||
'5',
|
||||
'6',
|
||||
);
|
||||
const digitsExhausted = {
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
...Object.fromEntries(
|
||||
[...'3456789'].map((digit) => [`prior-${digit}`, digit]),
|
||||
[...'456789'].map((digit) => [`prior-${digit}`, digit]),
|
||||
),
|
||||
};
|
||||
assert.equal(nextLayerStateToken(digitsExhausted), '00');
|
||||
@@ -307,8 +307,8 @@ test('production registry is exact, canonical, and rejects incomplete contracts'
|
||||
assert.equal(
|
||||
validateLayerStateAllocations(LAYER_STATE_TOKEN_RESERVATIONS, {
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
future: '3',
|
||||
next: '4',
|
||||
future: '4',
|
||||
next: '5',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
@@ -318,7 +318,7 @@ test('production registry is exact, canonical, and rejects incomplete contracts'
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
future: '00',
|
||||
}),
|
||||
/next free token 3/,
|
||||
/next free token 4/,
|
||||
);
|
||||
const beforeLastDigit = { ...digitsExhausted };
|
||||
delete beforeLastDigit['prior-9'];
|
||||
@@ -2639,8 +2639,8 @@ test('the recent-imagery split is share-link only: never stored locally, and a s
|
||||
);
|
||||
});
|
||||
|
||||
test('GNSS interference uses digit 0 without colliding with other digit tokens', () => {
|
||||
const decoded = decodeLayerStateParams(new URLSearchParams('v=2&l=0.2.1'));
|
||||
test('GNSS interference uses digit 3 without colliding with other digit tokens', () => {
|
||||
const decoded = decodeLayerStateParams(new URLSearchParams('v=2&l=3.2.1'));
|
||||
assert.deepEqual(decoded.enabledLayerIds, [
|
||||
'fire-perimeters',
|
||||
'gnss-interference',
|
||||
@@ -2648,7 +2648,7 @@ test('GNSS interference uses digit 0 without colliding with other digit tokens',
|
||||
]);
|
||||
assert.equal(
|
||||
LAYER_STATE_REGISTRY.find(({ id }) => id === 'gnss-interference').token,
|
||||
'0',
|
||||
'3',
|
||||
);
|
||||
assert.deepEqual(
|
||||
decodeLayerStateParams(new URLSearchParams(encode(decoded))),
|
||||
|
||||
@@ -128,10 +128,14 @@ test('reservation ledger is complete, pinned, and rejects duplicate or malformed
|
||||
{ ...parseLayerStateTokenReservations(reservationRows) },
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS },
|
||||
);
|
||||
assert.equal(Object.keys(LAYER_STATE_TOKEN_RESERVATIONS).length, 29);
|
||||
assert.equal(Object.keys(LAYER_STATE_TOKEN_RESERVATIONS).length, 30);
|
||||
assert.deepEqual(
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS },
|
||||
{ ...LEGACY_LAYER_STATE_TOKENS, 'street-level': '0', 'gnss-interference': '0' },
|
||||
{
|
||||
...LEGACY_LAYER_STATE_TOKENS,
|
||||
'street-level': '0',
|
||||
'gnss-interference': '3',
|
||||
},
|
||||
);
|
||||
assert.throws(
|
||||
() => parseLayerStateTokenReservations(reservationRows.slice(1)),
|
||||
@@ -151,9 +155,9 @@ test('reservation ledger is complete, pinned, and rejects duplicate or malformed
|
||||
/Duplicate layer-state token reservation/,
|
||||
);
|
||||
assert.equal(
|
||||
parseLayerStateTokenReservations([...reservationRows, ['future', '3']])
|
||||
parseLayerStateTokenReservations([...reservationRows, ['future', '4']])
|
||||
.future,
|
||||
'3',
|
||||
'4',
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
@@ -171,14 +175,14 @@ test('reservation ledger is complete, pinned, and rejects duplicate or malformed
|
||||
|
||||
test('published retired digits and competing PRs advance the merge-time allocation', () => {
|
||||
withPublishedBase(
|
||||
{ rows: [...reservationRows, ['retired-layer', '3']] },
|
||||
{ rows: [...reservationRows, ['retired-layer', '4']] },
|
||||
(cwd) => {
|
||||
const published = readPublishedLayerStateReservations('HEAD', cwd);
|
||||
assert.equal(nextLayerStateToken(published), '4');
|
||||
assert.equal(nextLayerStateToken(published), '5');
|
||||
assert.equal(
|
||||
validateLayerStateAllocations(published, {
|
||||
...published,
|
||||
newcomer: '4',
|
||||
newcomer: '5',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
@@ -186,15 +190,15 @@ test('published retired digits and competing PRs advance the merge-time allocati
|
||||
() =>
|
||||
validateLayerStateAllocations(published, {
|
||||
...published,
|
||||
competing: '3',
|
||||
competing: '4',
|
||||
}),
|
||||
/next free token 4/,
|
||||
/next free token 5/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
validateLayerStateAllocations(published, {
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
newcomer: '3',
|
||||
newcomer: '4',
|
||||
}),
|
||||
/changed or removed: retired-layer/,
|
||||
);
|
||||
@@ -202,8 +206,8 @@ test('published retired digits and competing PRs advance the merge-time allocati
|
||||
);
|
||||
});
|
||||
|
||||
test('PR B manually replaces provisional 3 with 4 after PR A publishes 3', () => {
|
||||
withPublishedBase({ rows: [...reservationRows, ['pr-a', '3']] }, (cwd) => {
|
||||
test('PR B manually replaces provisional 4 with 5 after PR A publishes 4', () => {
|
||||
withPublishedBase({ rows: [...reservationRows, ['pr-a', '4']] }, (cwd) => {
|
||||
mkdirSync(path.join(cwd, 'scripts'));
|
||||
const fixtureChecker = path.join(
|
||||
cwd,
|
||||
@@ -224,8 +228,8 @@ test('PR B manually replaces provisional 3 with 4 after PR A publishes 3', () =>
|
||||
|
||||
writeCodecFixture(
|
||||
cwd,
|
||||
[...reservationRows, ['pr-b', '3']],
|
||||
[{ id: 'pr-b', token: '3' }],
|
||||
[...reservationRows, ['pr-b', '4']],
|
||||
[{ id: 'pr-b', token: '4' }],
|
||||
);
|
||||
const beforeRebase = runCheck();
|
||||
assert.equal(beforeRebase.status, 1);
|
||||
@@ -233,10 +237,10 @@ test('PR B manually replaces provisional 3 with 4 after PR A publishes 3', () =>
|
||||
|
||||
writeCodecFixture(
|
||||
cwd,
|
||||
[...reservationRows, ['pr-a', '3'], ['pr-b', '3']],
|
||||
[...reservationRows, ['pr-a', '4'], ['pr-b', '4']],
|
||||
[
|
||||
{ id: 'pr-a', token: '3' },
|
||||
{ id: 'pr-b', token: '3' },
|
||||
{ id: 'pr-a', token: '4' },
|
||||
{ id: 'pr-b', token: '4' },
|
||||
],
|
||||
);
|
||||
const staleRebase = runCheck();
|
||||
@@ -244,16 +248,16 @@ test('PR B manually replaces provisional 3 with 4 after PR A publishes 3', () =>
|
||||
assert.match(staleRebase.stderr, /Duplicate layer-state token reservation/);
|
||||
|
||||
const published = readPublishedLayerStateReservations('HEAD', cwd);
|
||||
assert.equal(nextLayerStateToken(published), '4');
|
||||
const validBaselineRows = [...reservationRows, ['pr-a', '3']];
|
||||
writeCodecFixture(cwd, validBaselineRows, [{ id: 'pr-a', token: '3' }]);
|
||||
assert.equal(nextLayerStateToken(published), '5');
|
||||
const validBaselineRows = [...reservationRows, ['pr-a', '4']];
|
||||
writeCodecFixture(cwd, validBaselineRows, [{ id: 'pr-a', token: '4' }]);
|
||||
const helper = spawnSync(
|
||||
process.execPath,
|
||||
[realpathSync(fixtureNext), 'pr-b'],
|
||||
{ cwd, encoding: 'utf8' },
|
||||
);
|
||||
assert.equal(helper.status, 0, helper.stderr);
|
||||
assert.match(helper.stdout, /pr-b: 4/);
|
||||
assert.match(helper.stdout, /pr-b: 5/);
|
||||
assert.deepEqual(
|
||||
JSON.parse(readFileSync(path.join(cwd, LEDGER_PATH), 'utf8')),
|
||||
validBaselineRows,
|
||||
@@ -261,20 +265,20 @@ test('PR B manually replaces provisional 3 with 4 after PR A publishes 3', () =>
|
||||
);
|
||||
writeCodecFixture(
|
||||
cwd,
|
||||
[...reservationRows, ['pr-a', '3'], ['pr-b', '4']],
|
||||
[...reservationRows, ['pr-a', '4'], ['pr-b', '5']],
|
||||
[
|
||||
{ id: 'pr-a', token: '3' },
|
||||
{ id: 'pr-b', token: '4' },
|
||||
{ id: 'pr-a', token: '4' },
|
||||
{ id: 'pr-b', token: '5' },
|
||||
],
|
||||
);
|
||||
const corrected = runCheck();
|
||||
assert.equal(corrected.status, 0, corrected.stderr);
|
||||
assert.match(corrected.stdout, /30 published, 1 new/);
|
||||
assert.match(corrected.stdout, /31 published, 1 new/);
|
||||
});
|
||||
});
|
||||
|
||||
test('allocation batches cross the last digit and base-36 pair boundaries in order', () => {
|
||||
const digitRows = [...'3456789'].map((digit) => [`retired-${digit}`, digit]);
|
||||
const digitRows = [...'456789'].map((digit) => [`retired-${digit}`, digit]);
|
||||
const allDigits = parseLayerStateTokenReservations([
|
||||
...reservationRows,
|
||||
...digitRows,
|
||||
@@ -326,7 +330,7 @@ test('allocation batches cross the last digit and base-36 pair boundaries in ord
|
||||
test('an isolated valid two-character fixture round-trips an l field beyond the old 64-character cap', async () => {
|
||||
const cwd = mkdtempSync(path.join(tmpdir(), 'gev-layer-token-width-'));
|
||||
try {
|
||||
const priorDigits = [...'3456789'].map((digit) => [
|
||||
const priorDigits = [...'456789'].map((digit) => [
|
||||
`qa-prior-digit-${digit}`,
|
||||
digit,
|
||||
]);
|
||||
@@ -363,7 +367,7 @@ test('an isolated valid two-character fixture round-trips an l field beyond the
|
||||
const params = new URLSearchParams([['v', '2']]);
|
||||
codec.encodeLayerStateParams(params, state);
|
||||
assert.ok(params.get('l').length > 64, 'fixture must cross the old cap');
|
||||
assert.equal(params.get('l').length, 69);
|
||||
assert.equal(params.get('l').length, 71);
|
||||
const restored = codec.decodeLayerStateParams(params);
|
||||
assert.deepEqual(restored?.enabledLayerIds, expectedLayerIds);
|
||||
assert.deepEqual(restored?.options, state.options);
|
||||
@@ -381,7 +385,7 @@ test('checker reads a complete future base ledger and rejects retired-token reus
|
||||
},
|
||||
(cwd) => {
|
||||
const published = readPublishedLayerStateReservations('HEAD', cwd);
|
||||
assert.equal(Object.keys(published).length, 30);
|
||||
assert.equal(Object.keys(published).length, 31);
|
||||
assert.equal(published['retired-layer'], '00');
|
||||
assert.throws(
|
||||
() =>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
["earthquakes", "e"],
|
||||
["fire-perimeters", "2"],
|
||||
["flights", "f"],
|
||||
["gnss-interference", "0"],
|
||||
["gnss-interference", "3"],
|
||||
["local-dams", "q"],
|
||||
["local-datacenters", "d"],
|
||||
["local-firms", "w"],
|
||||
|
||||
+299
-111
@@ -24,8 +24,12 @@ test('share-Follow absence requires an accepted adsb.lol snapshot', async () =>
|
||||
(await militaryFlightsLayer.resolveTrackingRestoreTarget('ae1234')).status,
|
||||
'source-unavailable',
|
||||
);
|
||||
_setMilitaryTrackingRefreshOutcomeForTest({ status: 'accepted', ids: ['different'] });
|
||||
const missing = await militaryFlightsLayer.resolveTrackingRestoreTarget('ae1234');
|
||||
_setMilitaryTrackingRefreshOutcomeForTest({
|
||||
status: 'accepted',
|
||||
ids: ['different'],
|
||||
});
|
||||
const missing =
|
||||
await militaryFlightsLayer.resolveTrackingRestoreTarget('ae1234');
|
||||
assert.equal(missing.status, 'missing');
|
||||
assert.equal(missing.reason, 'target-absent-from-snapshot');
|
||||
});
|
||||
@@ -72,29 +76,12 @@ test('military analyst record: full record maps every contract field', () => {
|
||||
emergencyKind: null,
|
||||
emergencySource: null,
|
||||
emergencyBroadcast: null,
|
||||
observedAtMs: null,
|
||||
lastContactAtMs: null,
|
||||
routeOrigin: null,
|
||||
routeDestination: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('military analyst record: emergency flag is only raised for emergency severity', () => {
|
||||
const hijack = mapAnalystRecord('ae01ce', {
|
||||
...FULL_INFO,
|
||||
squawk: '7500',
|
||||
emergency: { kind: 'unlawful', severity: 'emergency', squawk: '7500' },
|
||||
});
|
||||
assert.equal(hijack.squawk, '7500');
|
||||
assert.equal(hijack.emergency, true);
|
||||
assert.equal(hijack.emergencyKind, 'unlawful');
|
||||
const lifeguard = mapAnalystRecord('ae01ce', {
|
||||
...FULL_INFO,
|
||||
emergency: { kind: 'lifeguard', severity: 'priority', squawk: null },
|
||||
});
|
||||
assert.equal(lifeguard.emergency, false);
|
||||
assert.equal(lifeguard.emergencyKind, 'lifeguard');
|
||||
});
|
||||
|
||||
test('military analyst record: military is ALWAYS true, routes/country always null', () => {
|
||||
const r = mapAnalystRecord('ae01ce', undefined);
|
||||
assert.equal(r.military, true);
|
||||
@@ -104,8 +91,14 @@ test('military analyst record: military is ALWAYS true, routes/country always nu
|
||||
});
|
||||
|
||||
test('military analyst record: no callsign falls back to registration, then icao24', () => {
|
||||
assert.equal(mapAnalystRecord('ae01ce', { ...FULL_INFO, callsign: '' }).id, '05-8152');
|
||||
assert.equal(mapAnalystRecord('ae01ce', { callsign: '', registration: ' ' }).id, 'ae01ce');
|
||||
assert.equal(
|
||||
mapAnalystRecord('ae01ce', { ...FULL_INFO, callsign: '' }).id,
|
||||
'05-8152',
|
||||
);
|
||||
assert.equal(
|
||||
mapAnalystRecord('ae01ce', { callsign: '', registration: ' ' }).id,
|
||||
'ae01ce',
|
||||
);
|
||||
});
|
||||
|
||||
test('military analyst record: empty info yields nulls, never NaN/undefined', () => {
|
||||
@@ -114,7 +107,8 @@ test('military analyst record: empty info yields nulls, never NaN/undefined', ()
|
||||
assert.equal(r.onGround, false);
|
||||
for (const [key, value] of Object.entries(r)) {
|
||||
assert.notEqual(value, undefined, `${key} must not be undefined`);
|
||||
if (typeof value === 'number') assert.ok(Number.isFinite(value), `${key} must not be NaN`);
|
||||
if (typeof value === 'number')
|
||||
assert.ok(Number.isFinite(value), `${key} must not be NaN`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -126,12 +120,17 @@ test('military analyst record: output is JSON-safe (no Cesium types)', () => {
|
||||
test('military first update forwards caller cancellation into the feed request', async () => {
|
||||
const realFetch = globalThis.fetch;
|
||||
let observedSignal = null;
|
||||
globalThis.fetch = (_url, options = {}) => new Promise((_resolve, reject) => {
|
||||
observedSignal = options.signal;
|
||||
options.signal?.addEventListener('abort', () => {
|
||||
reject(new DOMException('aborted', 'AbortError'));
|
||||
}, { once: true });
|
||||
});
|
||||
globalThis.fetch = (_url, options = {}) =>
|
||||
new Promise((_resolve, reject) => {
|
||||
observedSignal = options.signal;
|
||||
options.signal?.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
reject(new DOMException('aborted', 'AbortError'));
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const work = militaryFlightsLayer.update({}, { signal: controller.signal });
|
||||
@@ -161,11 +160,22 @@ test('nonempty adsb.lol payload with zero usable rows cannot prove share target
|
||||
json: async () => ({ ac: [null, {}, { hex: 'ae1234' }] }),
|
||||
});
|
||||
try {
|
||||
await militaryFlightsLayer.update({ camera: { positionCartographic: null }, scene: {} });
|
||||
const resolution = await militaryFlightsLayer.resolveTrackingRestoreTarget('ae1234');
|
||||
await militaryFlightsLayer.update({
|
||||
camera: { positionCartographic: null },
|
||||
scene: {},
|
||||
});
|
||||
const resolution =
|
||||
await militaryFlightsLayer.resolveTrackingRestoreTarget('ae1234');
|
||||
assert.equal(resolution.status, 'source-unavailable');
|
||||
assert.match(militaryFlightsLayer.getStats().error, /Malformed adsb\.lol aircraft rows/);
|
||||
assert.equal(militaryFlightsLayer.getAnalystRecords().length, 1, 'warm aircraft data is preserved');
|
||||
assert.match(
|
||||
militaryFlightsLayer.getStats().error,
|
||||
/Malformed adsb\.lol aircraft rows/,
|
||||
);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.getAnalystRecords().length,
|
||||
1,
|
||||
'warm aircraft data is preserved',
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = realFetch;
|
||||
}
|
||||
@@ -211,21 +221,26 @@ test('military poll refreshes tracked callsign/altitude/kts and marks a missed p
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
ac: poll++ === 0 ? [{
|
||||
hex: icao24,
|
||||
lon: -96.9,
|
||||
lat: 31.1,
|
||||
alt_baro: 28_000,
|
||||
alt_geom: 28_100,
|
||||
track: 95,
|
||||
gs: 400,
|
||||
seen: 0,
|
||||
seen_pos: 0,
|
||||
flight: 'RCH451 ',
|
||||
t: 'C17',
|
||||
r: '05-8152',
|
||||
ownOp: 'United States Air Force',
|
||||
}] : [],
|
||||
ac:
|
||||
poll++ === 0
|
||||
? [
|
||||
{
|
||||
hex: icao24,
|
||||
lon: -96.9,
|
||||
lat: 31.1,
|
||||
alt_baro: 28_000,
|
||||
alt_geom: 28_100,
|
||||
track: 95,
|
||||
gs: 400,
|
||||
seen: 0,
|
||||
seen_pos: 0,
|
||||
flight: 'RCH451 ',
|
||||
t: 'C17',
|
||||
r: '05-8152',
|
||||
ownOp: 'United States Air Force',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -250,7 +265,10 @@ test('cached military snapshots retain their observation time and expose stale f
|
||||
_setTrackedMilitaryRefreshStateForTest({
|
||||
icao24: 'ae01ce',
|
||||
entity: { gevLabelModel: { title: 'RCH451', details: [] } },
|
||||
billboard: { position: Cesium.Cartesian3.fromDegrees(-97, 31, 8000), show: false },
|
||||
billboard: {
|
||||
position: Cesium.Cartesian3.fromDegrees(-97, 31, 8000),
|
||||
show: false,
|
||||
},
|
||||
billboardCollection: { show: false, remove() {} },
|
||||
viewer,
|
||||
history,
|
||||
@@ -288,11 +306,18 @@ test('cached military snapshots retain their observation time and expose stale f
|
||||
await militaryFlightsLayer.update(viewer);
|
||||
assert.equal(history.length, 1);
|
||||
assert.equal(history[0].epochMs, receivedAt - 2000);
|
||||
for (const [status, age] of [['HIT', 5000], ['STALE', 45000]]) {
|
||||
for (const [status, age] of [
|
||||
['HIT', 5000],
|
||||
['STALE', 45000],
|
||||
]) {
|
||||
cache = status;
|
||||
now = receivedAt + age;
|
||||
await militaryFlightsLayer.update(viewer);
|
||||
assert.equal(history.length, 1, 'replayed positions cannot create a new fix');
|
||||
assert.equal(
|
||||
history.length,
|
||||
1,
|
||||
'replayed positions cannot create a new fix',
|
||||
);
|
||||
assert.equal(militaryFlightsLayer.getStats().lastUpdate, receivedAt);
|
||||
assert.equal(militaryFlightsLayer.getStats().stale, status === 'STALE');
|
||||
assert.equal(militaryFlightsLayer.getStats().error, null);
|
||||
@@ -319,14 +344,18 @@ test('real military track path creates no native label and publishes every cache
|
||||
trackedEntityChanged,
|
||||
clock: { currentTime: now },
|
||||
camera: {
|
||||
cancelFlight() { cancelledFlights += 1; },
|
||||
cancelFlight() {
|
||||
cancelledFlights += 1;
|
||||
},
|
||||
position: new Cesium.Cartesian3(0, -10_000, 7_000),
|
||||
positionWC: new Cesium.Cartesian3(0, -10_000, 7_000),
|
||||
direction: Cesium.Cartesian3.UNIT_Y,
|
||||
transform: Cesium.Matrix4.IDENTITY,
|
||||
viewMatrix: Cesium.Matrix4.IDENTITY,
|
||||
frustum: { projectionMatrix: Cesium.Matrix4.IDENTITY },
|
||||
lookAtTransform() { appliedFrames += 1; },
|
||||
lookAtTransform() {
|
||||
appliedFrames += 1;
|
||||
},
|
||||
},
|
||||
scene: {
|
||||
canvas: { clientWidth: 1600, clientHeight: 900 },
|
||||
@@ -365,9 +394,12 @@ test('real military track path creates no native label and publishes every cache
|
||||
const realFetch = globalThis.fetch;
|
||||
globalThis.window = new EventTarget();
|
||||
const selectionEvents = [];
|
||||
globalThis.window.addEventListener('gev:awareness-subject-selected', (event) => {
|
||||
selectionEvents.push(event.detail);
|
||||
});
|
||||
globalThis.window.addEventListener(
|
||||
'gev:awareness-subject-selected',
|
||||
(event) => {
|
||||
selectionEvents.push(event.detail);
|
||||
},
|
||||
);
|
||||
globalThis.fetch = async () => ({ ok: false });
|
||||
_setTrackedOverlayHostForTest(host);
|
||||
try {
|
||||
@@ -379,13 +411,15 @@ test('real military track path creates no native label and publishes every cache
|
||||
billboardCollection: { show: true, remove() {} },
|
||||
viewer,
|
||||
tracked: false,
|
||||
history: [{
|
||||
time: now,
|
||||
epochMs: Date.now(),
|
||||
position,
|
||||
velocity: 231.5,
|
||||
track: 92.1,
|
||||
}],
|
||||
history: [
|
||||
{
|
||||
time: now,
|
||||
epochMs: Date.now(),
|
||||
position,
|
||||
velocity: 231.5,
|
||||
track: 92.1,
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
...FULL_INFO,
|
||||
type: 'C17',
|
||||
@@ -395,34 +429,72 @@ test('real military track path creates no native label and publishes every cache
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(militaryFlightsLayer.trackById(icao24, { origin: 'programmatic' }), true);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.trackById(icao24, { origin: 'programmatic' }),
|
||||
true,
|
||||
);
|
||||
const entity = viewer.trackedEntity;
|
||||
assert.ok(entity instanceof Cesium.Entity, 'trackById must create the real Cesium entity');
|
||||
assert.ok(
|
||||
entity instanceof Cesium.Entity,
|
||||
'trackById must create the real Cesium entity',
|
||||
);
|
||||
assert.equal(entity.label, undefined);
|
||||
assert.ok(entities.values.every((candidate) => candidate.label === undefined));
|
||||
assert.ok(
|
||||
entities.values.every((candidate) => candidate.label === undefined),
|
||||
);
|
||||
assert.deepEqual(entity.gevLabelModel, {
|
||||
title: 'RCH451',
|
||||
details: [
|
||||
'C17 · 05-8152',
|
||||
'United States Air Force · 28000 ft · 450 kt',
|
||||
],
|
||||
details: ['C17 · 05-8152', 'United States Air Force · 28000 ft · 450 kt'],
|
||||
accent: '#ffd166',
|
||||
});
|
||||
viewer.scene.preUpdate.raiseEvent();
|
||||
const initialAppliedFrames = appliedFrames;
|
||||
const initialCancelledFlights = cancelledFlights;
|
||||
assert.equal(militaryFlightsLayer.trackById(icao24, { origin: 'user' }), true);
|
||||
assert.equal(cancelledFlights, initialCancelledFlights, 'ordinary repeated tracking stays camera-idempotent');
|
||||
assert.equal(selectionEvents.at(-1)?.origin, 'user', 'same-target selection upgrades durable authority');
|
||||
assert.equal(
|
||||
militaryFlightsLayer.trackById(icao24, { origin: 'user' }),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
cancelledFlights,
|
||||
initialCancelledFlights,
|
||||
'ordinary repeated tracking stays camera-idempotent',
|
||||
);
|
||||
assert.equal(
|
||||
selectionEvents.at(-1)?.origin,
|
||||
'user',
|
||||
'same-target selection upgrades durable authority',
|
||||
);
|
||||
assert.equal(entity.gevSelectionOrigin, 'user');
|
||||
assert.equal(militaryFlightsLayer.refocusTrackedById('different-flight'), false);
|
||||
assert.equal(militaryFlightsLayer.refocusTrackedById(icao24, { origin: 'voice' }), true);
|
||||
assert.equal(selectionEvents.at(-1)?.origin, 'voice', 'same-target refocus forwards explicit authority');
|
||||
assert.equal(
|
||||
militaryFlightsLayer.refocusTrackedById('different-flight'),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.refocusTrackedById(icao24, { origin: 'voice' }),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
selectionEvents.at(-1)?.origin,
|
||||
'voice',
|
||||
'same-target refocus forwards explicit authority',
|
||||
);
|
||||
assert.equal(militaryFlightsLayer.refocusTrackedById(icao24), true);
|
||||
viewer.scene.preUpdate.raiseEvent();
|
||||
assert.equal(viewer.trackedEntity, entity, 'refocus must retain the exact tracked entity');
|
||||
assert.equal(appliedFrames, initialAppliedFrames + 1, 'repeated refocus keeps one camera-frame owner');
|
||||
assert.equal(cancelledFlights, initialCancelledFlights + 2, 'only explicit refocus requests cancel camera flights');
|
||||
assert.equal(
|
||||
viewer.trackedEntity,
|
||||
entity,
|
||||
'refocus must retain the exact tracked entity',
|
||||
);
|
||||
assert.equal(
|
||||
appliedFrames,
|
||||
initialAppliedFrames + 1,
|
||||
'repeated refocus keeps one camera-frame owner',
|
||||
);
|
||||
assert.equal(
|
||||
cancelledFlights,
|
||||
initialCancelledFlights + 2,
|
||||
'only explicit refocus requests cancel camera flights',
|
||||
);
|
||||
const publication = publications.at(-1);
|
||||
assert.equal(publication.sourceId, 'tracked');
|
||||
const entry = publication.entries[0];
|
||||
@@ -433,23 +505,40 @@ test('real military track path creates no native label and publishes every cache
|
||||
|
||||
const display = entity.position.getValue(now);
|
||||
assert.ok(display, 'tracked position callback must seed its frame cache');
|
||||
assert.equal(entry.position(), display, 'host must reuse the exact cached display Cartesian');
|
||||
assert.equal(
|
||||
entry.position(),
|
||||
display,
|
||||
'host must reuse the exact cached display Cartesian',
|
||||
);
|
||||
|
||||
militaryFlightsLayer.setParams(
|
||||
{ selectedMilitaryTrackingId: 'late001' },
|
||||
{ origin: 'share-restore' },
|
||||
);
|
||||
assert.equal(_pendingMilitaryTrackingRestoreForTest(), 'late001');
|
||||
assert.equal(militaryFlightsLayer.trackById(icao24, { origin: 'user' }), true);
|
||||
assert.equal(_pendingMilitaryTrackingRestoreForTest(), null, 'new user selection cancels stale restore');
|
||||
assert.equal(
|
||||
militaryFlightsLayer.trackById(icao24, { origin: 'user' }),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
_pendingMilitaryTrackingRestoreForTest(),
|
||||
null,
|
||||
'new user selection cancels stale restore',
|
||||
);
|
||||
_addMilitaryTrackingCandidateForTest({
|
||||
icao24: 'late001',
|
||||
billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-96.9, 31.1, 8_000) },
|
||||
billboard: {
|
||||
...billboard,
|
||||
position: Cesium.Cartesian3.fromDegrees(-96.9, 31.1, 8_000),
|
||||
},
|
||||
meta: { ...FULL_INFO, callsign: 'LATE1', rawLat: 31.1, rawLon: -96.9 },
|
||||
history: [],
|
||||
});
|
||||
_applyPendingMilitaryTrackingRestoreForTest();
|
||||
assert.equal(militaryFlightsLayer.getParams().selectedMilitaryTrackingId, icao24);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.getParams().selectedMilitaryTrackingId,
|
||||
icao24,
|
||||
);
|
||||
|
||||
militaryFlightsLayer.stopTracking();
|
||||
militaryFlightsLayer.setParams(
|
||||
@@ -458,15 +547,25 @@ test('real military track path creates no native label and publishes every cache
|
||||
);
|
||||
assert.equal(_pendingMilitaryTrackingRestoreForTest(), 'late002');
|
||||
militaryFlightsLayer.stopTracking({ origin: 'user' });
|
||||
assert.equal(_pendingMilitaryTrackingRestoreForTest(), null, 'explicit clear cancels stale restore');
|
||||
assert.equal(
|
||||
_pendingMilitaryTrackingRestoreForTest(),
|
||||
null,
|
||||
'explicit clear cancels stale restore',
|
||||
);
|
||||
_addMilitaryTrackingCandidateForTest({
|
||||
icao24: 'late002',
|
||||
billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-96.8, 31.2, 7_000) },
|
||||
billboard: {
|
||||
...billboard,
|
||||
position: Cesium.Cartesian3.fromDegrees(-96.8, 31.2, 7_000),
|
||||
},
|
||||
meta: { ...FULL_INFO, callsign: 'LATE2', rawLat: 31.2, rawLon: -96.8 },
|
||||
history: [],
|
||||
});
|
||||
_applyPendingMilitaryTrackingRestoreForTest();
|
||||
assert.equal(militaryFlightsLayer.getParams().selectedMilitaryTrackingId, null);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.getParams().selectedMilitaryTrackingId,
|
||||
null,
|
||||
);
|
||||
|
||||
militaryFlightsLayer.setParams(
|
||||
{ selectedMilitaryTrackingId: 'late003' },
|
||||
@@ -481,12 +580,18 @@ test('real military track path creates no native label and publishes every cache
|
||||
);
|
||||
_addMilitaryTrackingCandidateForTest({
|
||||
icao24: 'late003',
|
||||
billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-97.3, 30.6, 7_000) },
|
||||
billboard: {
|
||||
...billboard,
|
||||
position: Cesium.Cartesian3.fromDegrees(-97.3, 30.6, 7_000),
|
||||
},
|
||||
meta: { ...FULL_INFO, callsign: 'LATE3', rawLat: 30.6, rawLon: -97.3 },
|
||||
history: [],
|
||||
});
|
||||
_applyPendingMilitaryTrackingRestoreForTest();
|
||||
assert.equal(militaryFlightsLayer.getParams().selectedMilitaryTrackingId, null);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.getParams().selectedMilitaryTrackingId,
|
||||
null,
|
||||
);
|
||||
|
||||
militaryFlightsLayer.setParams(
|
||||
{ selectedMilitaryTrackingId: 'late004' },
|
||||
@@ -495,18 +600,27 @@ test('real military track path creates no native label and publishes every cache
|
||||
assert.equal(_pendingMilitaryTrackingRestoreForTest(), 'late004');
|
||||
_addMilitaryTrackingCandidateForTest({
|
||||
icao24: 'late004',
|
||||
billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-97.2, 30.7, 6_000) },
|
||||
billboard: {
|
||||
...billboard,
|
||||
position: Cesium.Cartesian3.fromDegrees(-97.2, 30.7, 6_000),
|
||||
},
|
||||
meta: { ...FULL_INFO, callsign: 'LATE4', rawLat: 30.7, rawLon: -97.2 },
|
||||
history: [],
|
||||
});
|
||||
assert.equal(_applyPendingMilitaryTrackingRestoreForTest(), true);
|
||||
assert.equal(militaryFlightsLayer.getParams().selectedMilitaryTrackingId, 'late004');
|
||||
assert.equal(
|
||||
militaryFlightsLayer.getParams().selectedMilitaryTrackingId,
|
||||
'late004',
|
||||
);
|
||||
|
||||
militaryFlightsLayer.setParams(
|
||||
{ selectedMilitaryTrackingId: 'late005' },
|
||||
{ origin: 'local-restore' },
|
||||
);
|
||||
assert.equal(militaryFlightsLayer.trackById(icao24, { origin: 'programmatic' }), true);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.trackById(icao24, { origin: 'programmatic' }),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
_pendingMilitaryTrackingRestoreForTest(),
|
||||
'late005',
|
||||
@@ -514,12 +628,18 @@ test('real military track path creates no native label and publishes every cache
|
||||
);
|
||||
_addMilitaryTrackingCandidateForTest({
|
||||
icao24: 'late005',
|
||||
billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-96.6, 31.4, 5_000) },
|
||||
billboard: {
|
||||
...billboard,
|
||||
position: Cesium.Cartesian3.fromDegrees(-96.6, 31.4, 5_000),
|
||||
},
|
||||
meta: { ...FULL_INFO, callsign: 'LATE5', rawLat: 31.4, rawLon: -96.6 },
|
||||
history: [],
|
||||
});
|
||||
assert.equal(_applyPendingMilitaryTrackingRestoreForTest(), true);
|
||||
assert.equal(militaryFlightsLayer.getParams().selectedMilitaryTrackingId, 'late005');
|
||||
assert.equal(
|
||||
militaryFlightsLayer.getParams().selectedMilitaryTrackingId,
|
||||
'late005',
|
||||
);
|
||||
} finally {
|
||||
militaryFlightsLayer.stopTracking();
|
||||
viewer.scene.preUpdate.raiseEvent();
|
||||
@@ -564,21 +684,47 @@ test('a callsign-less contact is findable by the tail number the app displays',
|
||||
|
||||
// The identity analyst_query hands back for this contact.
|
||||
const analystId = mapAnalystRecord(icao24, {
|
||||
callsign: null, registration: '6606', rawLat: 40.71, rawLon: -74.01,
|
||||
callsign: null,
|
||||
registration: '6606',
|
||||
rawLat: 40.71,
|
||||
rawLon: -74.01,
|
||||
}).id;
|
||||
assert.equal(analystId, '6606', 'the analyst names a callsign-less contact by its tail');
|
||||
assert.equal(
|
||||
analystId,
|
||||
'6606',
|
||||
'the analyst names a callsign-less contact by its tail',
|
||||
);
|
||||
|
||||
const found = militaryFlightsLayer.findByQuery(analystId);
|
||||
assert.equal(found?.icao24, icao24, 'and the tracker must resolve that same identity');
|
||||
assert.equal(militaryFlightsLayer.findByQuery('660')?.icao24, icao24, 'prefix match too');
|
||||
assert.equal(militaryFlightsLayer.findByQuery(icao24)?.icao24, icao24, 'hex still wins outright');
|
||||
assert.equal(
|
||||
found?.icao24,
|
||||
icao24,
|
||||
'and the tracker must resolve that same identity',
|
||||
);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.findByQuery('660')?.icao24,
|
||||
icao24,
|
||||
'prefix match too',
|
||||
);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.findByQuery(icao24)?.icao24,
|
||||
icao24,
|
||||
'hex still wins outright',
|
||||
);
|
||||
});
|
||||
|
||||
test('the analyst record carries the hex key the tracker keys on', () => {
|
||||
const record = mapAnalystRecord('ae7f01', {
|
||||
callsign: null, registration: '6606', rawLat: 40.71, rawLon: -74.01,
|
||||
callsign: null,
|
||||
registration: '6606',
|
||||
rawLat: 40.71,
|
||||
rawLon: -74.01,
|
||||
});
|
||||
assert.equal(record.icao24, 'ae7f01', 'a resolvable key must ride along with the display label');
|
||||
assert.equal(
|
||||
record.icao24,
|
||||
'ae7f01',
|
||||
'a resolvable key must ride along with the display label',
|
||||
);
|
||||
});
|
||||
|
||||
test('a registration never outranks another contact’s exact callsign', () => {
|
||||
@@ -591,18 +737,35 @@ test('a registration never outranks another contact’s exact callsign', () => {
|
||||
show: true,
|
||||
});
|
||||
const base = {
|
||||
type: 'C17', operator: 'USAF', altitudeFt: 25000, speedMps: 180,
|
||||
track: 90, klass: 'widebody', onGround: false,
|
||||
type: 'C17',
|
||||
operator: 'USAF',
|
||||
altitudeFt: 25000,
|
||||
speedMps: 180,
|
||||
track: 90,
|
||||
klass: 'widebody',
|
||||
onGround: false,
|
||||
};
|
||||
// Registered FIRST, so insertion order favours it.
|
||||
_addMilitaryTrackingCandidateForTest({
|
||||
icao24: 'ae0aa1',
|
||||
meta: { ...base, callsign: null, registration: 'ZULU777', rawLat: 31.0, rawLon: -97.0 },
|
||||
meta: {
|
||||
...base,
|
||||
callsign: null,
|
||||
registration: 'ZULU777',
|
||||
rawLat: 31.0,
|
||||
rawLon: -97.0,
|
||||
},
|
||||
billboard: billboardAt(-97.0, 31.0),
|
||||
});
|
||||
_addMilitaryTrackingCandidateForTest({
|
||||
icao24: 'ae0bb2',
|
||||
meta: { ...base, callsign: 'ZULU777', registration: '05-9999', rawLat: 31.2, rawLon: -97.2 },
|
||||
meta: {
|
||||
...base,
|
||||
callsign: 'ZULU777',
|
||||
registration: '05-9999',
|
||||
rawLat: 31.2,
|
||||
rawLon: -97.2,
|
||||
},
|
||||
billboard: billboardAt(-97.2, 31.2),
|
||||
});
|
||||
|
||||
@@ -625,24 +788,49 @@ test('two contacts matching at the same strength resolve deterministically', ()
|
||||
show: true,
|
||||
});
|
||||
const base = {
|
||||
type: 'C130', operator: 'USAF', altitudeFt: 21000, speedMps: 150,
|
||||
track: 45, klass: 'turboprop', onGround: false,
|
||||
type: 'C130',
|
||||
operator: 'USAF',
|
||||
altitudeFt: 21000,
|
||||
speedMps: 150,
|
||||
track: 45,
|
||||
klass: 'turboprop',
|
||||
onGround: false,
|
||||
};
|
||||
// Both registrations begin with QQ7 — a same-tier prefix tie. Registered
|
||||
// highest-hex first so insertion order would pick the wrong one.
|
||||
_addMilitaryTrackingCandidateForTest({
|
||||
icao24: 'aef002',
|
||||
meta: { ...base, callsign: null, registration: 'QQ7-222', rawLat: 32.0, rawLon: -98.0 },
|
||||
meta: {
|
||||
...base,
|
||||
callsign: null,
|
||||
registration: 'QQ7-222',
|
||||
rawLat: 32.0,
|
||||
rawLon: -98.0,
|
||||
},
|
||||
billboard: billboardAt(-98.0, 32.0),
|
||||
});
|
||||
_addMilitaryTrackingCandidateForTest({
|
||||
icao24: 'aef001',
|
||||
meta: { ...base, callsign: null, registration: 'QQ7-111', rawLat: 32.1, rawLon: -98.1 },
|
||||
meta: {
|
||||
...base,
|
||||
callsign: null,
|
||||
registration: 'QQ7-111',
|
||||
rawLat: 32.1,
|
||||
rawLon: -98.1,
|
||||
},
|
||||
billboard: billboardAt(-98.1, 32.1),
|
||||
});
|
||||
|
||||
const first = militaryFlightsLayer.findByQuery('QQ7')?.icao24;
|
||||
const second = militaryFlightsLayer.findByQuery('qq7')?.icao24;
|
||||
assert.equal(first, 'aef001', 'the stable key (lowest hex) wins, not the feed order');
|
||||
assert.equal(second, first, 'and the same query resolves the same way every time');
|
||||
assert.equal(
|
||||
first,
|
||||
'aef001',
|
||||
'the stable key (lowest hex) wins, not the feed order',
|
||||
);
|
||||
assert.equal(
|
||||
second,
|
||||
first,
|
||||
'and the same query resolves the same way every time',
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user