From 03cd379c0e9ee74e08b7655d4caef12e21e53e64 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Tue, 3 Nov 2026 19:42:46 +0400 Subject: [PATCH] Cover a military ICAO reported by both feeds with edge-case tests --- src/data/militaryFlights.edgeCases.test.mjs | 225 ++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 src/data/militaryFlights.edgeCases.test.mjs diff --git a/src/data/militaryFlights.edgeCases.test.mjs b/src/data/militaryFlights.edgeCases.test.mjs new file mode 100644 index 000000000..71f35c1d4 --- /dev/null +++ b/src/data/militaryFlights.edgeCases.test.mjs @@ -0,0 +1,225 @@ +// Edge cases for the military layer from docs/migration/planet-separation/layers.md: +// one ICAO present in both feeds yields one target with the military card, and +// when the owning feed loses the contact the follow camera is released. +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import * as Cesium from 'cesium'; +import flightsLayer, { + _armFlightTrackingRestoreForTest, + _setTrackedFlightRefreshStateForTest, +} from './flights.js'; +import militaryFlightsLayer, { + _setTrackedMilitaryRefreshStateForTest, +} from './militaryFlights.js'; +import { + isMilitaryIcao, + registerMilitaryIcaos, + setMilitaryLayerActive, +} from './militaryRegistry.js'; + +const SHARED_HEX = 'ae5e01'; +const CIVIL_HEX = 'a0c1v1'; + +function collection() { + const added = []; + return { + added, + show: true, + add(options) { + const billboard = { ...options }; + added.push(billboard); + return billboard; + }, + remove() {}, + }; +} + +function militaryRow(fields = {}) { + return { + hex: SHARED_HEX, + lon: -97.1, + lat: 31.1, + alt_baro: 24_000, + alt_geom: 24_100, + track: 90, + gs: 380, + seen: 0, + seen_pos: 0, + flight: 'RCH77 ', + t: 'C17', + r: '05-8152', + ownOp: 'United States Air Force', + ...fields, + }; +} + +function openSkyState(icao24, callsign, nowSec, lon, lat) { + return [ + icao24, + callsign, + 'United States', + nowSec - 2, + nowSec - 2, + lon, + lat, + 7_000, + false, + 200, + 90, + 0, + null, + 7_050, + null, + null, + 0, + ]; +} + +test('edge:military: an ICAO in both feeds draws one target and keeps the military card', async () => { + const realFetch = globalThis.fetch; + const realWindow = globalThis.window; + globalThis.window = new EventTarget(); + const viewer = { camera: { positionCartographic: null }, scene: {} }; + const milBillboards = collection(); + const civilBillboards = collection(); + _armFlightTrackingRestoreForTest(null); + _setTrackedMilitaryRefreshStateForTest({ + icao24: 'seedm0', + entity: null, + billboard: { show: false, position: Cesium.Cartesian3.ZERO }, + billboardCollection: milBillboards, + viewer, + meta: { rawLat: 0, rawLon: 0, onGround: false }, + tracked: false, + }); + _setTrackedFlightRefreshStateForTest({ + icao24: 'seedf0', + entity: null, + billboard: { show: false, position: Cesium.Cartesian3.ZERO }, + billboardCollection: civilBillboards, + viewer, + meta: { rawLat: 0, rawLon: 0, onGround: false }, + tracked: false, + }); + const nowSec = Math.floor(Date.now() / 1000); + globalThis.fetch = async (url) => { + if (String(url).startsWith('/api/flights')) { + return { + ok: true, + status: 200, + headers: { get: () => null }, + json: async () => ({ + time: nowSec, + states: [ + // The civil feed reports the same airframe under its civil callsign. + openSkyState(SHARED_HEX, 'CIVDUP ', nowSec, -97.11, 31.11), + openSkyState(CIVIL_HEX, 'SWA1 ', nowSec, -97.5, 31.5), + ], + }), + }; + } + return { + ok: true, + status: 200, + json: async () => ({ ac: [militaryRow()] }), + }; + }; + try { + setMilitaryLayerActive(true); + await militaryFlightsLayer.update(viewer); + assert.equal(isMilitaryIcao(SHARED_HEX), true); + await flightsLayer.update(viewer); + + const civilIds = civilBillboards.added.map((billboard) => billboard.id); + assert.ok(civilIds.includes(CIVIL_HEX), 'the civil contact is drawn'); + assert.equal( + civilIds.includes(SHARED_HEX), + false, + 'the civil duplicate of a military ICAO is not drawn', + ); + assert.equal(flightsLayer.hasContact(SHARED_HEX), false); + assert.equal(militaryFlightsLayer.hasContact(SHARED_HEX), true); + + const [card] = militaryFlightsLayer + .getAnalystRecords() + .filter((record) => record.icao24 === SHARED_HEX); + assert.equal(card.military, true); + assert.equal(card.callsign, 'RCH77'); + assert.equal( + flightsLayer + .getAnalystRecords() + .some((record) => record.icao24 === SHARED_HEX), + false, + 'no second card for the same airframe', + ); + } finally { + setMilitaryLayerActive(false); + globalThis.fetch = realFetch; + globalThis.window = realWindow; + } +}); + +test('edge:military: losing the tracked contact from the owning feed releases the camera', async () => { + const realFetch = globalThis.fetch; + const realWindow = globalThis.window; + globalThis.window = new EventTarget(); + const entity = { gevLabelModel: { title: 'RCH77', details: [] } }; + const viewer = { + camera: { positionCartographic: null }, + scene: {}, + entities: { add: (value) => value, remove: () => true }, + trackedEntity: entity, + }; + _setTrackedMilitaryRefreshStateForTest({ + icao24: SHARED_HEX, + entity, + billboard: { + show: false, + position: Cesium.Cartesian3.fromDegrees(-97.1, 31.1, 7_300), + color: Cesium.Color.WHITE, + }, + billboardCollection: collection(), + viewer, + meta: { + callsign: 'RCH77', + type: 'C17', + klass: 'widebody', + altitudeFt: 24_000, + renderAltitudeM: 7_300, + speedMps: 195, + track: 90, + onGround: false, + wasAirborne: true, + turnRateDps: 0, + rawLat: 31.1, + rawLon: -97.1, + }, + }); + globalThis.fetch = async () => ({ + ok: true, + status: 200, + json: async () => ({ ac: [militaryRow({ hex: 'ae0ther' })] }), + }); + try { + setMilitaryLayerActive(true); + registerMilitaryIcaos([SHARED_HEX]); + assert.equal(militaryFlightsLayer.getTrackedInfo()?.icao24, SHARED_HEX); + for (let poll = 0; poll < 6; poll += 1) { + await militaryFlightsLayer.update(viewer); + if (!militaryFlightsLayer.hasContact(SHARED_HEX)) break; + } + assert.equal(militaryFlightsLayer.hasContact(SHARED_HEX), false); + assert.equal( + viewer.trackedEntity, + undefined, + 'the follow camera is released', + ); + assert.equal(militaryFlightsLayer.getTrackedInfo(), null); + // The civil layer does not silently take the camera over. + assert.equal(flightsLayer.getTrackedInfo?.() ?? null, null); + } finally { + setMilitaryLayerActive(false); + globalThis.fetch = realFetch; + globalThis.window = realWindow; + } +});