meshai/work/tests/test_satpass_handler.py
Ubuntu 2e1fb325f7 refactor: move source tree into work/, multi-stage Docker build, fix satpass
- Move all application source (meshai/, dashboard-frontend/, tests/,
  config/, docs/, Dockerfile, etc.) into work/ directory
- Add Node.js multi-stage build to Dockerfile for frontend compilation;
  remove compiled static assets from git tracking
- Fix satpass missing time windows: consolidation was splitting wire on
  newline and only putting line 1 in event.title, dropping the time
  window line that the composer uses for precomposed broadcasts
- Fix satpass burst flooding: stagger consolidation timers (+60s per
  pending pass) so Central batch publishes don't blast the mesh
- Update CI workflow build context to work/
- Anchor lib/ and data/ gitignore patterns to repo root to prevent
  false matches on nested directories
- Add dashboard-frontend/node_modules/ to .dockerignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-16 03:40:31 +00:00

173 lines
6.5 KiB
Python

"""v0.7 satpass_handler tests."""
import pytest
from unittest.mock import MagicMock, patch
def _envelope(norad_id=25544, sat_name="ISS", observer="Boise",
max_el=75.0, aos="2026-06-12T03:32:00Z",
los="2026-06-12T03:38:00Z", direction="NW-SE",
aos_compass="SW", los_compass="NE"):
"""Build a CloudEvents envelope for a satellite pass."""
return {
"specversion": "1.0",
"type": "central.sat.pass",
"source": "central",
"id": f"pass-{norad_id}-{aos}",
"data": {
"adapter": "n2yo_visualpasses",
"category": "pass.n2yo_visualpasses",
"severity": 0,
"data": {
"norad_id": norad_id,
"satellite_name": sat_name,
"observer_name": observer,
"max_elevation_deg": max_el,
"aos_time": aos,
"los_time": los,
"azimuth_at_peak_compass": direction,
"azimuth_at_aos_compass": aos_compass,
"azimuth_at_los_compass": los_compass,
}
}
}
@pytest.fixture
def mock_db():
"""Mock database connection."""
conn = MagicMock()
conn.execute.return_value.fetchone.return_value = None
conn.execute.return_value.lastrowid = 1
with patch("meshai.central.satpass_handler.get_db", return_value=conn):
yield conn
@pytest.fixture
def mock_adapter_config():
"""Mock adapter_config.satpass."""
cfg = MagicMock()
cfg.enabled = True
cfg.observers = [] # empty = all observers
cfg.min_elevation = 30
cfg.norad_ids = [25544] # must be non-empty for opt-in
cfg.dry_run = False
cfg.max_broadcasts_per_hour = 4
with patch("meshai.central.satpass_handler.adapter_config") as mock:
mock.satpass = cfg
from meshai.central.satpass_handler import handle_satpass
if hasattr(handle_satpass, "_disabled_logged"):
del handle_satpass._disabled_logged
if hasattr(handle_satpass, "_no_norad_ids_logged"):
del handle_satpass._no_norad_ids_logged
yield cfg
class TestSatpassHandler:
"""Tests for handle_satpass function."""
def test_high_elevation_pass_broadcasts(self, mock_db, mock_adapter_config):
"""A pass with high elevation should broadcast."""
from meshai.central.satpass_handler import handle_satpass
env = _envelope(max_el=75.0)
result = handle_satpass(env, "central.sat.pass.iss", data={}, now=1718163120)
assert result is not None
assert "ISS" in result
def test_low_elevation_pass_filtered(self, mock_db, mock_adapter_config):
"""A pass below min_elevation should be filtered."""
from meshai.central.satpass_handler import handle_satpass
mock_adapter_config.min_elevation = 30
env = _envelope(max_el=25.0)
result = handle_satpass(env, "central.sat.pass.iss", data={}, now=1718163120)
assert result is None
def test_observer_filter_blocks_mismatch(self, mock_db, mock_adapter_config):
"""A pass for non-configured observer should be filtered."""
from meshai.central.satpass_handler import handle_satpass
mock_adapter_config.observers = ["Magic Valley"]
env = _envelope(observer="Boise")
result = handle_satpass(env, "central.sat.pass.iss", data={}, now=1718163120)
assert result is None
def test_observer_filter_allows_match(self, mock_db, mock_adapter_config):
"""A pass for configured observer should broadcast."""
from meshai.central.satpass_handler import handle_satpass
mock_adapter_config.observers = ["Boise", "Magic Valley"]
env = _envelope(observer="Boise", max_el=45.0)
result = handle_satpass(env, "central.sat.pass.iss", data={}, now=1718163120)
assert result is not None
def test_norad_id_filter(self, mock_db, mock_adapter_config):
"""NORAD ID filter should block non-matching satellites."""
from meshai.central.satpass_handler import handle_satpass
mock_adapter_config.norad_ids = [25544] # ISS only
env = _envelope(norad_id=12345, max_el=60.0)
result = handle_satpass(env, "central.sat.pass.other", data={}, now=1718163120)
assert result is None
def test_dedup_blocks_second_broadcast(self, mock_db, mock_adapter_config):
"""Second pass in same hour bucket should be deduplicated."""
from meshai.central.satpass_handler import handle_satpass
# First call returns no existing broadcast
mock_db.execute.return_value.fetchone.return_value = None
env = _envelope(max_el=60.0)
result1 = handle_satpass(env, "central.sat.pass.iss", data={}, now=1718163120)
assert result1 is not None
# Second call simulates existing broadcast
mock_db.execute.return_value.fetchone.return_value = {"last_broadcast_at": 1718163120}
result2 = handle_satpass(env, "central.sat.pass.iss", data={}, now=1718163180)
assert result2 is None
def test_wire_format(self, mock_db, mock_adapter_config):
"""Wire format should have 2 lines with correct info."""
from meshai.central.satpass_handler import handle_satpass
env = _envelope(sat_name="ISS", max_el=75, observer="Boise",
direction="NW-SE", aos_compass="SW", los_compass="NE")
result = handle_satpass(env, "central.sat.pass.iss", data={}, now=1718163120)
lines = result.split("\n")
assert len(lines) == 2
assert "ISS" in lines[0]
assert "overhead" in lines[0]
assert "SW" in lines[0]
assert "NE" in lines[0]
assert "minute window" in lines[1]
def test_commit_callback_attached(self, mock_db, mock_adapter_config):
"""Broadcast should attach commit callback."""
from meshai.central.satpass_handler import handle_satpass
data = {}
env = _envelope(max_el=60.0)
result = handle_satpass(env, "central.sat.pass.iss", data=data, now=1718163120)
assert result is not None
assert "_on_broadcast_committed" in data
assert "_broadcast_audit" in data
assert data["_broadcast_audit"]["table"] == "satpass_events"
def test_wrong_adapter_ignored(self, mock_db, mock_adapter_config):
"""Envelope with wrong adapter should be ignored."""
from meshai.central.satpass_handler import handle_satpass
env = _envelope()
env["data"]["adapter"] = "some_other_adapter"
result = handle_satpass(env, "central.sat.pass.iss", data={}, now=1718163120)
assert result is None