Wire AIS end to end: telemetry ingestion + live map overlay

node-26#9, same shape as the ADS-B commit. Adds POST /telemetry/ais
(same node-key auth, same org_id-stamped upsert-by-key pattern, this time
by mmsi into a new `vessels` collection) and its docInMyOrg() firestore
rule. Frontend gets useVessels() (mirrors useAircraft(), longer staleness
window since AIS position reports are minutes apart, not seconds) and an
opt-in "Vessels" map overlay.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-09-20 16:12:56 -04:00
co-authored by Claude Sonnet 5
parent 5537b095df
commit 3d2b722c64
7 changed files with 213 additions and 0 deletions
+34
View File
@@ -58,3 +58,37 @@ def test_node_upload_skips_entries_missing_icao():
assert resp.status_code == 200
assert resp.json() == {"ok": True, "count": 0}
mock_set.assert_not_awaited()
def test_ais_service_token_without_node_id_is_rejected():
_override({"service": True})
resp = client.post("/telemetry/ais", json={"vessels": []})
assert resp.status_code == 400
def test_ais_node_upload_upserts_and_stamps_org_id():
_override({"node": True, "node_id": "node-1"})
with patch.object(telemetry.fstore, "doc_get_cached", AsyncMock(return_value={"org_id": "org-A"})), \
patch.object(telemetry.fstore, "doc_set", AsyncMock()) as mock_set:
resp = client.post("/telemetry/ais", json={
"vessels": [{"mmsi": "123456789", "name": "MV TEST", "lat": 41.0, "lon": -73.9}],
})
assert resp.status_code == 200
assert resp.json() == {"ok": True, "count": 1}
mock_set.assert_awaited_once()
(collection, doc_id, doc), kwargs = mock_set.await_args
assert collection == "vessels"
assert doc_id == "123456789"
assert doc["node_id"] == "node-1"
assert doc["org_id"] == "org-A"
assert kwargs.get("merge") is True
def test_ais_node_upload_skips_entries_missing_mmsi():
_override({"node": True, "node_id": "node-1"})
with patch.object(telemetry.fstore, "doc_get_cached", AsyncMock(return_value=None)), \
patch.object(telemetry.fstore, "doc_set", AsyncMock()) as mock_set:
resp = client.post("/telemetry/ais", json={"vessels": [{"mmsi": ""}]})
assert resp.status_code == 200
assert resp.json() == {"ok": True, "count": 0}
mock_set.assert_not_awaited()