Implement story 2.1: aircraft data model and fetcher
Add HttpFetcher and FileFixtureFetcher with shared _parse_aircraft helper, DUMP1090_URL constant, realistic fixture data, and full test coverage for all acceptance criteria (AC1–AC5). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+124
-2
@@ -1,2 +1,124 @@
|
||||
def test_placeholder() -> None:
|
||||
pass
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from planemapper.fetcher import FileFixtureFetcher, HttpFetcher
|
||||
|
||||
_FIXTURES = Path(__file__).parent / "fixtures" / "aircraft_sample.json"
|
||||
|
||||
_FULL_RESPONSE = {
|
||||
"aircraft": [
|
||||
{
|
||||
"hex": "4ca7f2",
|
||||
"lat": 53.3498,
|
||||
"lon": -6.2603,
|
||||
"flight": "EIN123 ",
|
||||
"altitude": 12000,
|
||||
"category": "A3",
|
||||
"track": 270.0,
|
||||
"mlat": [],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
_DEFAULTS_RESPONSE = {
|
||||
"aircraft": [
|
||||
{
|
||||
"hex": "aabbcc",
|
||||
"lat": 51.0,
|
||||
"lon": -1.0,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
_MLAT_RESPONSE = {
|
||||
"aircraft": [
|
||||
{
|
||||
"hex": "dddddd",
|
||||
"lat": 52.0,
|
||||
"lon": -2.0,
|
||||
"mlat": ["lat", "lon"],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def _mock_get(payload: dict) -> MagicMock:
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = payload
|
||||
mock_resp.raise_for_status = MagicMock()
|
||||
return mock_resp
|
||||
|
||||
|
||||
def test_http_fetcher_full_response() -> None:
|
||||
with patch("planemapper.fetcher.requests.get", return_value=_mock_get(_FULL_RESPONSE)):
|
||||
aircraft = HttpFetcher().fetch()
|
||||
assert len(aircraft) == 1
|
||||
a = aircraft[0]
|
||||
assert a.icao == "4ca7f2"
|
||||
assert a.lat == 53.3498
|
||||
assert a.lon == -6.2603
|
||||
assert a.callsign == "EIN123"
|
||||
assert a.altitude_ft == 12000
|
||||
assert a.category == "A3"
|
||||
assert a.heading == 270.0
|
||||
assert a.is_mlat is False
|
||||
assert a.is_stale is False
|
||||
|
||||
|
||||
def test_http_fetcher_missing_fields_use_defaults() -> None:
|
||||
with patch("planemapper.fetcher.requests.get", return_value=_mock_get(_DEFAULTS_RESPONSE)):
|
||||
aircraft = HttpFetcher().fetch()
|
||||
assert len(aircraft) == 1
|
||||
a = aircraft[0]
|
||||
assert a.callsign == ""
|
||||
assert a.altitude_ft == 0
|
||||
assert a.category == ""
|
||||
assert a.is_mlat is False
|
||||
|
||||
|
||||
def test_http_fetcher_altitude_ground_string() -> None:
|
||||
payload = {"aircraft": [{"hex": "aaa", "lat": 51.0, "lon": -1.0, "altitude": "ground"}]}
|
||||
with patch("planemapper.fetcher.requests.get", return_value=_mock_get(payload)):
|
||||
aircraft = HttpFetcher().fetch()
|
||||
assert aircraft[0].altitude_ft == 0
|
||||
|
||||
|
||||
def test_http_fetcher_skips_entries_without_position() -> None:
|
||||
payload = {"aircraft": [{"hex": "nopos"}, {"hex": "haspos", "lat": 51.0, "lon": -1.0}]}
|
||||
with patch("planemapper.fetcher.requests.get", return_value=_mock_get(payload)):
|
||||
aircraft = HttpFetcher().fetch()
|
||||
assert len(aircraft) == 1
|
||||
assert aircraft[0].icao == "haspos"
|
||||
|
||||
|
||||
def test_http_fetcher_propagates_timeout() -> None:
|
||||
with patch("planemapper.fetcher.requests.get", side_effect=requests.Timeout):
|
||||
with pytest.raises(requests.Timeout):
|
||||
HttpFetcher().fetch()
|
||||
|
||||
|
||||
def test_http_fetcher_mlat_flag() -> None:
|
||||
with patch("planemapper.fetcher.requests.get", return_value=_mock_get(_MLAT_RESPONSE)):
|
||||
aircraft = HttpFetcher().fetch()
|
||||
assert aircraft[0].is_mlat is True
|
||||
|
||||
|
||||
def test_file_fixture_fetcher_returns_aircraft() -> None:
|
||||
aircraft = FileFixtureFetcher(_FIXTURES).fetch()
|
||||
assert len(aircraft) == 4
|
||||
|
||||
|
||||
def test_file_fixture_fetcher_no_network_call() -> None:
|
||||
with patch("planemapper.fetcher.requests.get") as mock_get:
|
||||
FileFixtureFetcher(_FIXTURES).fetch()
|
||||
mock_get.assert_not_called()
|
||||
|
||||
|
||||
def test_file_fixture_fetcher_mlat_aircraft() -> None:
|
||||
aircraft = FileFixtureFetcher(_FIXTURES).fetch()
|
||||
mlat_aircraft = [a for a in aircraft if a.is_mlat]
|
||||
assert len(mlat_aircraft) == 1
|
||||
assert mlat_aircraft[0].icao == "4003c3"
|
||||
|
||||
Reference in New Issue
Block a user