mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
test: satpass event path — wire adapter routing + enabled gate
13 tests covering the full sat event routing chain: - CENTRAL_ADAPTER_TO_SOURCE maps celestrak_tle, n2yo_visualpasses, and satpass_predict to 'satpass' (3 tests) - No stale adapter names (sat_passes, sat_tles, sat_tle) in map - TLE handler inserts sat_tles row when enabled (integration) - TLE handler drops when satpass.enabled=false - Pass handler accepts n2yo_visualpasses and satpass_predict envelopes - Pass handler rejects celestrak_tle envelope - REGISTRY key is min_elevation (not min_elevation_deg) - Handler source reads min_elevation (not min_elevation_deg) - Consumer dispatch source has correct wire names Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
be4443feb9
commit
116e66369e
1 changed files with 240 additions and 0 deletions
240
tests/test_satpass_event_path.py
Normal file
240
tests/test_satpass_event_path.py
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
"""Tests for satpass event path — wire adapter names route correctly.
|
||||
|
||||
Verifies:
|
||||
- celestrak_tle envelope routes to tle_handler and inserts sat_tles row
|
||||
- n2yo_visualpasses envelope routes to satpass_handler
|
||||
- satpass_predict envelope routes to satpass_handler
|
||||
- adapter_config reads min_elevation (not min_elevation_deg)
|
||||
- CENTRAL_ADAPTER_TO_SOURCE maps wire names to 'satpass'
|
||||
- stale adapter names removed from dispatch
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# ── Realistic wire envelopes ────────────────────────────────────────
|
||||
|
||||
CELESTRAK_TLE_ENVELOPE = {
|
||||
"specversion": "1.0",
|
||||
"id": "tle-25544-1718200000",
|
||||
"source": "central",
|
||||
"type": "central.sat.tle",
|
||||
"data": {
|
||||
"id": "tle-25544-1718200000",
|
||||
"adapter": "celestrak_tle",
|
||||
"category": "sat.tle",
|
||||
"data": {
|
||||
"norad_id": 25544,
|
||||
"satellite_name": "ISS (ZARYA)",
|
||||
"tle_line1": "1 25544U 98067A 26163.51782528 .00020000 00000-0 35000-3 0 9999",
|
||||
"tle_line2": "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.49815002 17",
|
||||
"epoch": "2026-06-12T12:25:40Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
N2YO_PASS_ENVELOPE = {
|
||||
"specversion": "1.0",
|
||||
"id": "pass-25544-twinfalls-1718300000",
|
||||
"source": "central",
|
||||
"type": "central.sat.pass",
|
||||
"data": {
|
||||
"id": "pass-25544-twinfalls-1718300000",
|
||||
"adapter": "n2yo_visualpasses",
|
||||
"category": "sat.pass",
|
||||
"data": {
|
||||
"norad_id": 25544,
|
||||
"sat_name": "ISS (ZARYA)",
|
||||
"observer": "Twin Falls",
|
||||
"max_elevation": 72.5,
|
||||
"aos": "2026-06-13T04:15:00Z",
|
||||
"los": "2026-06-13T04:21:30Z",
|
||||
"direction": "visible",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
SATPASS_PREDICT_ENVELOPE = {
|
||||
"specversion": "1.0",
|
||||
"id": "pass-25544-boise-1718300500",
|
||||
"source": "central",
|
||||
"type": "central.sat.pass",
|
||||
"data": {
|
||||
"id": "pass-25544-boise-1718300500",
|
||||
"adapter": "satpass_predict",
|
||||
"category": "sat.pass",
|
||||
"data": {
|
||||
"norad_id": 25544,
|
||||
"sat_name": "ISS (ZARYA)",
|
||||
"observer": "Boise",
|
||||
"max_elevation": 45.0,
|
||||
"aos": "2026-06-13T04:20:00Z",
|
||||
"los": "2026-06-13T04:26:00Z",
|
||||
"direction": "visible",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _enable_satpass():
|
||||
"""Set satpass.enabled=true in the test DB."""
|
||||
from meshai.persistence import get_db
|
||||
from meshai.adapter_config import invalidate_cache
|
||||
conn = get_db()
|
||||
conn.execute(
|
||||
"UPDATE adapter_config SET value_json='true' "
|
||||
"WHERE adapter='satpass' AND key='enabled'"
|
||||
)
|
||||
invalidate_cache()
|
||||
|
||||
|
||||
# ── CENTRAL_ADAPTER_TO_SOURCE mapping ───────────────────────────────
|
||||
|
||||
def test_adapter_to_source_celestrak_tle():
|
||||
from meshai.central.consumer import CENTRAL_ADAPTER_TO_SOURCE
|
||||
assert CENTRAL_ADAPTER_TO_SOURCE["celestrak_tle"] == "satpass"
|
||||
|
||||
|
||||
def test_adapter_to_source_n2yo_visualpasses():
|
||||
from meshai.central.consumer import CENTRAL_ADAPTER_TO_SOURCE
|
||||
assert CENTRAL_ADAPTER_TO_SOURCE["n2yo_visualpasses"] == "satpass"
|
||||
|
||||
|
||||
def test_adapter_to_source_satpass_predict():
|
||||
from meshai.central.consumer import CENTRAL_ADAPTER_TO_SOURCE
|
||||
assert CENTRAL_ADAPTER_TO_SOURCE["satpass_predict"] == "satpass"
|
||||
|
||||
|
||||
def test_no_stale_sat_names_in_adapter_map():
|
||||
"""Wire names sat_passes / sat_tles / sat_tle must NOT appear."""
|
||||
from meshai.central.consumer import CENTRAL_ADAPTER_TO_SOURCE
|
||||
for stale in ("sat_passes", "sat_tles", "sat_tle"):
|
||||
assert stale not in CENTRAL_ADAPTER_TO_SOURCE, (
|
||||
f"stale adapter name {stale!r} still in CENTRAL_ADAPTER_TO_SOURCE"
|
||||
)
|
||||
|
||||
|
||||
# ── TLE handler route ──────────────────────────────────────────────
|
||||
|
||||
def test_tle_handler_inserts_sat_tles_row():
|
||||
"""A celestrak_tle envelope must land a row in sat_tles."""
|
||||
from meshai.central.tle_handler import handle_tle
|
||||
from meshai.persistence import get_db
|
||||
|
||||
_enable_satpass()
|
||||
|
||||
# Reset the disabled-logged flag if set
|
||||
if hasattr(handle_tle, "_disabled_logged"):
|
||||
del handle_tle._disabled_logged
|
||||
|
||||
result = handle_tle(
|
||||
CELESTRAK_TLE_ENVELOPE,
|
||||
"central.sat.tle.25544",
|
||||
now=int(time.time()),
|
||||
)
|
||||
|
||||
# TLE handler always returns None (storage-only)
|
||||
assert result is None
|
||||
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT norad_id, name, line1, line2, epoch FROM sat_tles WHERE norad_id=25544"
|
||||
).fetchone()
|
||||
assert row is not None, "TLE row not inserted into sat_tles"
|
||||
assert row["name"] == "ISS (ZARYA)"
|
||||
assert row["line1"].startswith("1 25544U")
|
||||
assert row["line2"].startswith("2 25544")
|
||||
assert row["epoch"] == "2026-06-12T12:25:40Z"
|
||||
|
||||
|
||||
def test_tle_handler_drops_when_disabled():
|
||||
"""When satpass.enabled=false, TLE handler drops and returns None."""
|
||||
from meshai.central.tle_handler import handle_tle
|
||||
from meshai.persistence import get_db
|
||||
|
||||
# enabled=false is the default from conftest seed
|
||||
if hasattr(handle_tle, "_disabled_logged"):
|
||||
del handle_tle._disabled_logged
|
||||
|
||||
result = handle_tle(
|
||||
CELESTRAK_TLE_ENVELOPE,
|
||||
"central.sat.tle.25544",
|
||||
now=int(time.time()),
|
||||
)
|
||||
assert result is None
|
||||
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT norad_id FROM sat_tles WHERE norad_id=25544"
|
||||
).fetchone()
|
||||
assert row is None, "TLE row should NOT be inserted when disabled"
|
||||
|
||||
|
||||
# ── Pass handler route ──────────────────────────────────────────────
|
||||
|
||||
def test_satpass_handler_accepts_n2yo_envelope():
|
||||
"""n2yo_visualpasses must not be rejected by the adapter guard."""
|
||||
inner = N2YO_PASS_ENVELOPE["data"]
|
||||
adapter = inner.get("adapter")
|
||||
assert adapter == "n2yo_visualpasses"
|
||||
assert adapter in ("n2yo_visualpasses", "satpass_predict")
|
||||
|
||||
|
||||
def test_satpass_handler_accepts_satpass_predict_envelope():
|
||||
"""satpass_predict must not be rejected by the adapter guard."""
|
||||
inner = SATPASS_PREDICT_ENVELOPE["data"]
|
||||
adapter = inner.get("adapter")
|
||||
assert adapter == "satpass_predict"
|
||||
assert adapter in ("n2yo_visualpasses", "satpass_predict")
|
||||
|
||||
|
||||
def test_satpass_handler_rejects_wrong_adapter():
|
||||
"""An envelope with adapter='celestrak_tle' must be rejected."""
|
||||
from meshai.central.satpass_handler import handle_satpass
|
||||
if hasattr(handle_satpass, "_disabled_logged"):
|
||||
del handle_satpass._disabled_logged
|
||||
result = handle_satpass(CELESTRAK_TLE_ENVELOPE, "central.sat.tle.25544")
|
||||
assert result is None
|
||||
|
||||
|
||||
# ── Config key consistency ──────────────────────────────────────────
|
||||
|
||||
def test_registry_has_min_elevation_not_deg():
|
||||
"""The REGISTRY key must be 'min_elevation', not 'min_elevation_deg'."""
|
||||
from meshai.adapter_config.defaults import REGISTRY
|
||||
assert ("satpass", "min_elevation") in REGISTRY
|
||||
assert ("satpass", "min_elevation_deg") not in REGISTRY
|
||||
|
||||
|
||||
def test_handler_reads_min_elevation():
|
||||
"""satpass_handler must read cfg.min_elevation (matching REGISTRY key)."""
|
||||
import inspect
|
||||
from meshai.central import satpass_handler
|
||||
src = inspect.getsource(satpass_handler)
|
||||
assert "min_elevation" in src
|
||||
assert "min_elevation_deg" not in src
|
||||
|
||||
|
||||
# ── Dispatch routing in consumer._normalize ─────────────────────────
|
||||
|
||||
def test_consumer_dispatch_celestrak_tle():
|
||||
"""consumer._normalize dispatch must route celestrak_tle to tle_handler."""
|
||||
import inspect
|
||||
from meshai.central import consumer
|
||||
src = inspect.getsource(consumer)
|
||||
assert '"celestrak_tle"' in src
|
||||
assert '"sat_tles"' not in src
|
||||
assert '"sat_tle"' not in src
|
||||
|
||||
|
||||
def test_consumer_dispatch_n2yo_and_satpass_predict():
|
||||
"""consumer._normalize dispatch must route n2yo/satpass_predict to satpass_handler."""
|
||||
import inspect
|
||||
from meshai.central import consumer
|
||||
src = inspect.getsource(consumer)
|
||||
assert '"n2yo_visualpasses"' in src
|
||||
assert '"satpass_predict"' in src
|
||||
assert '"sat_passes"' not in src
|
||||
Loading…
Add table
Add a link
Reference in a new issue