osint-dashboard/tests/test_api_live_layers.py
Sirius DevOps 32320635dc
All checks were successful
build-and-deploy / build (push) Successful in 2m32s
feat: AIS stream follows the map viewport
The Vessels layer now retunes the server-side AISStream subscription to the
client viewport instead of a static AISSTREAM_BBOX. The frontend POSTs its
quantized viewport box to /api/vessels/subscribe on moveend; the ais_stream
worker coalesces and applies it at the service's 1 subscription/s cap, then
last-known positions for the new area arrive within a couple of seconds (the
frontend does one follow-up fetch after retuning). Bounds the in-memory
vessel store across regions. Key stays server-side.
2026-08-27 21:50:38 -04:00

77 lines
2.7 KiB
Python

"""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)
async def _post(path: str, payload: dict | None) -> httpx.Response:
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url=BASE) as client:
return await client.post(path, json=payload)
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() == []
assert "max-age" in (resp.headers.get("cache-control") or "").lower()
def _desired_boxes():
import asyncio as _a
from ais_stream import _take_desired_boxes
return _a.run(_take_desired_boxes())
def test_vessels_subscribe_sets_viewport_box():
assert _desired_boxes() is None
resp = asyncio.run(_post("/api/vessels/subscribe", {"bbox": "-70,40,-60,45"}))
assert resp.status_code == 200
body = resp.json()
assert body["ok"] is True and body["bbox"] == "-70,40,-60,45"
# AISStream corner order: [[lat, lon], [lat, lon]] (southwest, northeast).
assert _desired_boxes() == [[[40.0, -70.0], [45.0, -60.0]]]
def test_vessels_subscribe_empty_resets():
assert asyncio.run(_post("/api/vessels/subscribe", {"bbox": "-70,40,-60,45"})).status_code == 200
resp = asyncio.run(_post("/api/vessels/subscribe", {"bbox": ""}))
assert resp.status_code == 200
assert resp.json()["bbox"] is None
assert _desired_boxes() is None
def test_vessels_subscribe_rejects_bad_bbox():
for bad in ("1,2,3", "a,b,c,d", "20,30,10,40", "0,0,0,200"):
resp = asyncio.run(_post("/api/vessels/subscribe", {"bbox": bad}))
assert resp.status_code == 422, bad
# Explicit null bbox is the "reset to env default" path (still 200).
assert asyncio.run(_post("/api/vessels/subscribe", {"bbox": None})).status_code == 200