Implement story 1.4: location resolution ICAO and address
Adds location.resolve() with ICAO CSV lookup and Nominatim geocoding, POST /find-location portal route with inline confirmation/error display, and full test coverage with mocked network calls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +1,55 @@
|
||||
def test_placeholder() -> None:
|
||||
pass
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from planemapper.provisioning.location import resolve
|
||||
|
||||
|
||||
def test_icao_lookup_hit_egll() -> None:
|
||||
lat, lon, name = resolve("EGLL")
|
||||
assert isinstance(lat, float)
|
||||
assert isinstance(lon, float)
|
||||
assert isinstance(name, str)
|
||||
assert 50.0 < lat < 52.0 # Heathrow is ~51.47°N
|
||||
assert -1.0 < lon < 0.0 # and ~0.46°W
|
||||
|
||||
|
||||
def test_icao_lookup_case_insensitive() -> None:
|
||||
lat, lon, name = resolve("egll")
|
||||
assert lat != 0.0
|
||||
|
||||
|
||||
def test_icao_lookup_miss_raises_value_error() -> None:
|
||||
with pytest.raises(ValueError, match="ICAO code not found"):
|
||||
resolve("ZZZZ")
|
||||
|
||||
|
||||
def test_nominatim_success() -> None:
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = [
|
||||
{"lat": "51.5", "lon": "-0.1", "display_name": "London, England"}
|
||||
]
|
||||
with patch("planemapper.provisioning.location.requests.get", return_value=mock_resp):
|
||||
lat, lon, name = resolve("OX1 1AA")
|
||||
assert lat == 51.5
|
||||
assert lon == -0.1
|
||||
assert name == "London, England"
|
||||
|
||||
|
||||
def test_nominatim_empty_raises_value_error() -> None:
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = []
|
||||
with patch("planemapper.provisioning.location.requests.get", return_value=mock_resp):
|
||||
with pytest.raises(ValueError, match="Location not found"):
|
||||
resolve("nonsense query that returns nothing")
|
||||
|
||||
|
||||
def test_nominatim_called_with_user_agent() -> None:
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = [{"lat": "51.5", "lon": "-0.1", "display_name": "London"}]
|
||||
with patch(
|
||||
"planemapper.provisioning.location.requests.get", return_value=mock_resp
|
||||
) as mock_get:
|
||||
resolve("London")
|
||||
call_kwargs = mock_get.call_args
|
||||
assert "User-Agent" in call_kwargs.kwargs.get("headers", {})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from planemapper.provisioning.portal import app
|
||||
@@ -45,3 +47,25 @@ def test_ncsi_redirects_to_index(client) -> None:
|
||||
def test_unknown_route_redirects_to_index(client) -> None:
|
||||
resp = client.get("/some/random/path")
|
||||
assert resp.status_code in (301, 302)
|
||||
|
||||
|
||||
def test_find_location_success(client) -> None:
|
||||
mock_resolve = patch(
|
||||
"planemapper.provisioning.portal.location.resolve", return_value=(51.5, -0.1, "London")
|
||||
)
|
||||
with mock_resolve:
|
||||
resp = client.post("/find-location", data={"location": "EGLL", "radius": "100"})
|
||||
assert resp.status_code == 200
|
||||
data = resp.data.decode()
|
||||
assert "London" in data
|
||||
assert "51.5" in data
|
||||
|
||||
|
||||
def test_find_location_error(client) -> None:
|
||||
with patch(
|
||||
"planemapper.provisioning.portal.location.resolve",
|
||||
side_effect=ValueError("ICAO code not found — try an address instead"),
|
||||
):
|
||||
resp = client.post("/find-location", data={"location": "ZZZZ", "radius": "100"})
|
||||
assert resp.status_code == 200
|
||||
assert "ICAO code not found" in resp.data.decode()
|
||||
|
||||
Reference in New Issue
Block a user