"""Unit-level tests for the AZ-404 e2e helpers. Runs unconditionally in the regular regression suite (NOT gated by ``RUN_REPLAY_E2E``) — the helpers are pure / deterministic and test themselves cheaply. Covers AC-9 (Helper L2 computation correct) and ancillary helper invariants. """ from __future__ import annotations from pathlib import Path import pytest from tests.e2e.replay._helpers import ( CapturingMavlinkTransport, GroundTruthRow, l2_horizontal_m, match_percentage, parse_jsonl, ) # ---------------------------------------------------------------------- # AC-9: L2 helper correctness def test_ac9_l2_zero_at_same_point() -> None: # Arrange / Act d = l2_horizontal_m(50.08, 36.11, 50.08, 36.11) # Assert assert d == pytest.approx(0.0, abs=1e-6) def test_ac9_l2_north_one_degree_111km() -> None: """One degree of latitude ≈ 111 km on the WGS84 spherical model.""" # Act d = l2_horizontal_m(50.08, 36.11, 51.08, 36.11) # Assert assert d == pytest.approx(111_195.0, rel=0.001) def test_ac9_l2_known_pair_kharkiv_kyiv() -> None: """Hand-checked Derkachi (~Kharkiv) to Kyiv center: 411 km ± 1 km.""" # Arrange kharkiv_lat, kharkiv_lon = 49.9935, 36.2304 kyiv_lat, kyiv_lon = 50.4501, 30.5234 # Act d = l2_horizontal_m(kharkiv_lat, kharkiv_lon, kyiv_lat, kyiv_lon) # Assert — externally known reference distance is 411 km. assert d == pytest.approx(411_000.0, rel=0.005) def test_ac9_l2_symmetric() -> None: # Arrange a = (49.991, 36.221) b = (50.080, 36.111) # Act d_ab = l2_horizontal_m(*a, *b) d_ba = l2_horizontal_m(*b, *a) # Assert assert d_ab == pytest.approx(d_ba, rel=1e-12) # ---------------------------------------------------------------------- # match_percentage def test_match_percentage_all_within_threshold() -> None: # Arrange gt = [GroundTruthRow(t_s=0.0, lat_deg=50.0, lon_deg=36.0, alt_m=100.0)] emissions = [ { "emitted_at": 0, "position_wgs84": {"lat_deg": 50.0, "lon_deg": 36.0, "alt_m": 100.0}, } ] # Act pct = match_percentage(emissions, gt, threshold_m=100.0) # Assert assert pct == 1.0 def test_match_percentage_none_within_threshold() -> None: # Arrange gt = [GroundTruthRow(t_s=0.0, lat_deg=50.0, lon_deg=36.0, alt_m=100.0)] emissions = [ { "emitted_at": 0, # ~111 km north of the GT row. "position_wgs84": {"lat_deg": 51.0, "lon_deg": 36.0, "alt_m": 100.0}, } ] # Act pct = match_percentage(emissions, gt, threshold_m=100.0) # Assert assert pct == 0.0 def test_match_percentage_empty_emissions_zero() -> None: # Arrange gt = [GroundTruthRow(t_s=0.0, lat_deg=50.0, lon_deg=36.0, alt_m=100.0)] # Act pct = match_percentage([], gt, threshold_m=100.0) # Assert assert pct == 0.0 def test_match_percentage_empty_ground_truth_raises() -> None: # Act / Assert with pytest.raises(AssertionError, match="ground_truth must be non-empty"): match_percentage( [{"emitted_at": 0, "position_wgs84": {"lat_deg": 50, "lon_deg": 36}}], [], threshold_m=100.0, ) # ---------------------------------------------------------------------- # parse_jsonl def test_parse_jsonl_round_trip(tmp_path: Path) -> None: # Arrange path = tmp_path / "out.jsonl" path.write_text('{"a": 1}\n{"b": 2}\n') # Act rows = parse_jsonl(path) # Assert assert rows == [{"a": 1}, {"b": 2}] def test_parse_jsonl_skips_trailing_blank(tmp_path: Path) -> None: # Arrange path = tmp_path / "out.jsonl" path.write_text('{"a": 1}\n\n') # Act rows = parse_jsonl(path) # Assert — the trailing blank line is tolerated assert rows == [{"a": 1}] def test_parse_jsonl_invalid_line_raises(tmp_path: Path) -> None: # Arrange path = tmp_path / "out.jsonl" path.write_text("not json\n") # Act / Assert with pytest.raises(AssertionError, match="not valid JSON"): parse_jsonl(path) # ---------------------------------------------------------------------- # CapturingMavlinkTransport (ready for AZ-558 unblock) def test_capturing_transport_records_writes() -> None: # Arrange t = CapturingMavlinkTransport() # Act t.write(b"abc") t.write(b"def") # Assert assert t.captured_payloads == (b"abc", b"def") assert t.captured_concat == b"abcdef" assert t.bytes_written() == 6 def test_capturing_transport_close_then_write_raises() -> None: # Arrange t = CapturingMavlinkTransport() t.close() # Act / Assert with pytest.raises(RuntimeError, match="after close"): t.write(b"x") def test_capturing_transport_implements_protocol() -> None: # Arrange from gps_denied_onboard.components.c8_fc_adapter.interface import MavlinkTransport # Act t = CapturingMavlinkTransport() # Assert — runtime_checkable Protocol acceptance assert isinstance(t, MavlinkTransport)