feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
|
|
|
"""API contract tests for live overlay endpoints (no DB required)."""
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
|
|
|
|
|
import httpx
|
|
|
|
|
|
|
|
|
|
from live_layers import overlay_catalog
|
|
|
|
|
from main import app
|
|
|
|
|
|
|
|
|
|
BASE = "http://test"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _get(path: str) -> httpx.Response:
|
|
|
|
|
transport = httpx.ASGITransport(app=app)
|
|
|
|
|
async with httpx.AsyncClient(transport=transport, base_url=BASE) as client:
|
|
|
|
|
return await client.get(path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_map_layers_includes_overlays():
|
|
|
|
|
body = asyncio.run(_get("/api/map/layers")).json()
|
|
|
|
|
assert "layers" in body
|
|
|
|
|
overlays = body["overlays"]
|
|
|
|
|
for key in ("radar_iem", "radar_rainviewer", "gibs_thermal",
|
|
|
|
|
"aircraft", "vessels", "trains", "nws_alerts",
|
|
|
|
|
"wfigs_incidents", "wfigs_perimeters"):
|
|
|
|
|
assert key in overlays
|
|
|
|
|
assert overlay_catalog()["radar_iem"]["tileUrl"].startswith("https://mesonet")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_aircraft_requires_bbox():
|
|
|
|
|
resp = asyncio.run(_get("/api/aircraft"))
|
|
|
|
|
assert resp.status_code == 422
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vessels_empty_without_ais_key():
|
|
|
|
|
resp = asyncio.run(_get("/api/vessels"))
|
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
assert resp.json() == []
|
2026-08-27 21:21:19 -04:00
|
|
|
assert "max-age" in (resp.headers.get("cache-control") or "").lower()
|