"""Tests for arautopilot.core.nmea_data -- Sprint 5.""" from __future__ import annotations import math import time from unittest.mock import patch import pytest from arautopilot.core.nmea_data import CogSogData, HeadingData, NmeaNavData, XteData class TestCogSogData: def test_initial_state_invalid(self): d = CogSogData() assert not d.is_valid assert math.isnan(d.cog_deg) assert math.isnan(d.sog_kn) def test_update_marks_valid(self): d = CogSogData() d.update(270.0, 8.5) assert d.is_valid assert d.cog_deg == pytest.approx(270.0) assert d.sog_kn == pytest.approx(8.5) def test_cog_wraps_360(self): d = CogSogData() d.update(370.0, 5.0) assert d.cog_deg == pytest.approx(10.0) def test_cog_zero_stays_zero(self): d = CogSogData() d.update(0.0, 5.0) assert d.cog_deg == pytest.approx(0.0) def test_stale_after_max_age(self): d = CogSogData(max_age_s=1.0) d.update(90.0, 6.0) assert d.is_valid with patch("arautopilot.core.nmea_data.time") as mock_time: mock_time.monotonic.return_value = d.timestamp + 2.0 assert not d.is_valid def test_age_ms(self): d = CogSogData() d.update(180.0, 10.0) assert d.age_ms >= 0 class TestXteData: def test_initial_invalid(self): d = XteData() assert not d.is_valid assert math.isnan(d.xte_m) def test_update_positive_xte(self): """Positive XTE = vessel to starboard of track.""" d = XteData() d.update(xte_m=5.0, dtw_m=200.0, waypoint_name="WP01") assert d.is_valid assert d.xte_m == pytest.approx(5.0) assert d.dtw_m == pytest.approx(200.0) assert d.waypoint_name == "WP01" def test_update_negative_xte(self): d = XteData() d.update(xte_m=-3.5) assert d.xte_m == pytest.approx(-3.5) def test_stale_after_max_age(self): d = XteData(max_age_s=2.0) d.update(1.0) with patch("arautopilot.core.nmea_data.time") as mock_time: mock_time.monotonic.return_value = d.timestamp + 3.0 assert not d.is_valid def test_update_without_optional_fields(self): d = XteData() d.update(xte_m=0.0) assert d.is_valid assert math.isnan(d.dtw_m) assert d.waypoint_name == "" class TestNmeaNavData: def test_aggregate_has_all_fields(self): nav = NmeaNavData() assert isinstance(nav.heading, HeadingData) assert isinstance(nav.cog_sog, CogSogData) assert isinstance(nav.xte, XteData) def test_independent_updates(self): nav = NmeaNavData() nav.cog_sog.update(45.0, 7.0) nav.xte.update(2.5) assert nav.cog_sog.is_valid assert nav.xte.is_valid assert not nav.heading.is_valid