agenda/tests/test_trip.py

276 lines
8.1 KiB
Python

"""Tests for trip map coordinate assembly."""
import json
import pathlib
from datetime import date
import agenda.trip
import pytest
from agenda.types import Trip
from web_view import app
def test_add_coordinates_for_unbooked_flights_adds_missing_airports() -> None:
"""Unbooked routes should contribute missing airport pins."""
routes = [
{
"type": "unbooked_flight",
"key": "LHR_Paris_fr",
"from_iata": "LHR",
"to_iata": "CDG",
"from": (51.47, -0.45),
"to": (49.01, 2.55),
}
]
coordinates = [
{
"name": "Heathrow Airport",
"type": "airport",
"latitude": 51.47,
"longitude": -0.45,
}
]
airports = {
"LHR": {
"name": "Heathrow Airport",
"latitude": 51.47,
"longitude": -0.45,
},
"CDG": {
"name": "Paris Charles de Gaulle Airport",
"latitude": 49.01,
"longitude": 2.55,
},
}
with app.app_context():
original_parse_yaml = agenda.trip.travel.parse_yaml
try:
agenda.trip.travel.parse_yaml = lambda _name, _data_dir: airports
agenda.trip.add_coordinates_for_unbooked_flights(
routes, coordinates, app.config["PERSONAL_DATA"]
)
finally:
agenda.trip.travel.parse_yaml = original_parse_yaml
airport_names = {
coord["name"] for coord in coordinates if coord["type"] == "airport"
}
assert airport_names == {"Heathrow Airport", "Paris Charles de Gaulle Airport"}
def test_get_coordinates_and_routes_adds_unbooked_flight_airports() -> None:
"""Trip list map data should include pins for unbooked flights."""
trips = [Trip(start=date(2026, 7, 20))]
unbooked_routes = [
{
"type": "unbooked_flight",
"key": "LHR_Paris_fr",
"from_iata": "LHR",
"to_iata": "CDG",
"from": (51.47, -0.45),
"to": (49.01, 2.55),
}
]
airports = {
"LHR": {
"name": "Heathrow Airport",
"latitude": 51.47,
"longitude": -0.45,
},
"CDG": {
"name": "Paris Charles de Gaulle Airport",
"latitude": 49.01,
"longitude": 2.55,
},
}
with app.app_context():
original_collect_trip_coordinates = agenda.trip.collect_trip_coordinates
original_get_trip_routes = agenda.trip.get_trip_routes
original_parse_yaml = agenda.trip.travel.parse_yaml
try:
agenda.trip.collect_trip_coordinates = lambda _trip: []
agenda.trip.get_trip_routes = lambda _trip, _data_dir: unbooked_routes
agenda.trip.travel.parse_yaml = lambda _name, _data_dir: airports
coordinates, _routes = agenda.trip.get_coordinates_and_routes(trips)
finally:
agenda.trip.collect_trip_coordinates = original_collect_trip_coordinates
agenda.trip.get_trip_routes = original_get_trip_routes
agenda.trip.travel.parse_yaml = original_parse_yaml
airport_names = {
coord["name"] for coord in coordinates if coord["type"] == "airport"
}
assert airport_names == {"Heathrow Airport", "Paris Charles de Gaulle Airport"}
def test_get_trip_routes_assumes_unbooked_paris_trip_is_by_train(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Paris conferences without booked travel should show rail, not flight."""
trip = Trip(
start=date(2026, 7, 20),
conferences=[
{
"name": "Paris Conf",
"location": "Paris",
"country": "fr",
}
],
)
stations = [
{
"name": "London St Pancras",
"latitude": 51.531921,
"longitude": -0.126361,
"routes": {"Paris Gare du Nord": "London_St_Pancras_to_Paris_Gare_du_Nord"},
},
{
"name": "Paris Gare du Nord",
"latitude": 48.88111111111111,
"longitude": 2.355277777777778,
"routes": {"London St Pancras": "London_St_Pancras_to_Paris_Gare_du_Nord"},
},
]
def fake_parse_yaml(name: str, data_dir: str) -> object:
if name == "stations":
return stations
if name == "airports":
return {
"LHR": {
"name": "Heathrow Airport",
"latitude": 51.47,
"longitude": -0.45,
},
"CDG": {
"name": "Paris Charles de Gaulle Airport",
"city": "Paris",
"country": "fr",
"latitude": 49.01,
"longitude": 2.55,
},
}
raise AssertionError(f"unexpected YAML load: {name}")
monkeypatch.setattr(agenda.trip.travel, "parse_yaml", fake_parse_yaml)
monkeypatch.setattr(
agenda.trip, "load_flight_destination_rules", lambda _data_dir: []
)
routes = agenda.trip.get_trip_routes(trip, "/tmp/personal-data")
assert routes == [
{
"type": "train",
"key": "train_London St Pancras_Paris Gare du Nord",
"geojson_filename": "train_routes/London_St_Pancras_to_Paris_Gare_du_Nord",
}
]
def test_load_cars_infers_route_labels_and_home_marker(
tmp_path: pathlib.Path,
) -> None:
"""Car journeys should load direct GeoJSON routes and home endpoint markers."""
(tmp_path / "car_routes").mkdir()
(tmp_path / "car_journeys.yaml").write_text("""---
- trip: 2026-07-16
depart: 2026-07-16
arrive: 2026-07-16
route: PCH_to_EMF.geojson
""")
(tmp_path / "car_routes" / "PCH_to_EMF.geojson").write_text(
json.dumps(
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [[-2.60283, 51.44083], [-1.2, 52.0]],
},
}
)
)
cars = agenda.trip.load_cars(str(tmp_path))
assert len(cars) == 1
assert cars[0]["type"] == "car"
assert cars[0]["from"] == "PCH"
assert cars[0]["to"] == "EMF"
assert cars[0]["geojson_filename"] == "PCH_to_EMF"
assert cars[0]["distance"] > 0
assert cars[0]["from_location"] == {
"name": "PCH",
"type": "home",
"latitude": 51.44083,
"longitude": -2.60283,
}
assert "to_location" not in cars[0]
def test_load_cars_can_show_endpoint_markers(tmp_path: pathlib.Path) -> None:
"""Car endpoint markers should be opt-in."""
(tmp_path / "car_routes").mkdir()
(tmp_path / "car_journeys.yaml").write_text("""---
- trip: 2026-07-16
depart: 2026-07-16
arrive: 2026-07-16
route: PCH_to_EMF.geojson
show_markers: true
""")
(tmp_path / "car_routes" / "PCH_to_EMF.geojson").write_text(
json.dumps(
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [[-2.60283, 51.44083], [-1.2, 52.0]],
},
}
)
)
cars = agenda.trip.load_cars(str(tmp_path))
assert cars[0]["from_location"] == {
"name": "PCH",
"type": "home",
"latitude": 51.44083,
"longitude": -2.60283,
}
assert cars[0]["to_location"] == {
"name": "EMF",
"type": "car_stop",
"latitude": 52.0,
"longitude": -1.2,
}
def test_get_trip_routes_includes_car_geojson() -> None:
"""Car routes should render from the car_routes directory."""
trip = Trip(
start=date(2026, 7, 16),
travel=[
{
"type": "car",
"from": "PCH",
"to": "EMF",
"geojson_filename": "PCH_to_EMF",
}
],
)
routes = agenda.trip.get_trip_routes(trip, "/tmp/personal-data")
assert routes == [
{
"type": "car",
"key": "car_PCH_EMF_PCH_to_EMF",
"geojson_filename": "car_routes/PCH_to_EMF",
}
]