""" node-26#9 — second-SDR ADS-B telemetry ingestion. Two things matter here: the endpoint requires node identity (a service/admin token has no node_id to attribute the sighting to, so it must 400 rather than silently write an orphan doc), and org_id gets stamped from the node's own Firestore doc so firestore.rules' docInMyOrg() can gate the frontend's read — the same defensive-stamp pattern upload.py already uses for `calls`. """ from unittest.mock import AsyncMock, patch from fastapi.testclient import TestClient from app.main import app from app.internal.auth import require_node_service_or_firebase_token from app.routers import telemetry client = TestClient(app) def _override(decoded: dict): app.dependency_overrides[require_node_service_or_firebase_token] = lambda: decoded def teardown_function(): app.dependency_overrides.pop(require_node_service_or_firebase_token, None) def test_service_token_without_node_id_is_rejected(): _override({"service": True}) resp = client.post("/telemetry/adsb", json={"aircraft": []}) assert resp.status_code == 400 def test_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/adsb", json={ "aircraft": [{"icao": "A1B2C3", "callsign": "UAL123", "lat": 41.1, "lon": -73.8}], }) 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 == "aircraft" assert doc_id == "A1B2C3" assert doc["node_id"] == "node-1" assert doc["org_id"] == "org-A" assert kwargs.get("merge") is True def test_node_upload_skips_entries_missing_icao(): _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/adsb", json={"aircraft": [{"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()