"""Tests for trip list template rendering. Covers edge cases found in real data that caused UndefinedError / AttributeError: 1. Flight where 'arrive' key is entirely absent (older data format). Jinja2 returns Undefined for missing dict keys, which is truthy, so a plain truthiness check on item.arrive would pass and then .date() would raise UndefinedError. 2. Train where depart/arrive are date objects (not datetime). datetime.date has no .date() method, so item.depart.date() raises AttributeError (surfaced as UndefinedError inside Jinja2). """ from datetime import date, datetime, timezone import flask import pytest import web_view from agenda.types import TripElement # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- class _FakeTrip: """Minimal trip stub sufficient for render_trip_element.""" show_flags = False def _render(element: TripElement) -> str: """Render a single TripElement via the render_trip_element macro.""" with web_view.app.app_context(): with web_view.app.test_request_context(): flask.g.user = web_view.User(is_authenticated=False) macros = web_view.app.jinja_env.get_template("macros.html") return macros.module.render_trip_element(element, _FakeTrip()) def _flight(detail: dict) -> TripElement: return TripElement( start_time=detail["depart"], title="test flight", element_type="flight", detail=detail, start_loc="Origin", end_loc="Dest", ) def _train(detail: dict) -> TripElement: return TripElement( start_time=detail["depart"], title="test train", element_type="train", detail=detail, start_loc=detail["from"], end_loc=detail["to"], ) # --------------------------------------------------------------------------- # Unit tests — specific edge cases # --------------------------------------------------------------------------- def test_flight_missing_arrive_key() -> None: """Flight with no 'arrive' key must not raise UndefinedError. Older trips store flights without an arrival time. Jinja2 returns Undefined (truthy!) for absent dict keys, so a bare truthiness check was insufficient — only 'is defined' correctly guards against it. """ element = _flight( { "depart": datetime(2023, 7, 1, 13, 20, tzinfo=timezone.utc), # 'arrive' key intentionally absent "from": "TIA", "to": "LHR", "flight_number": "BA123", "airline_code": "BA", "airline_name": "British Airways", "distance": 2000.0, } ) result = _render(element) assert "Origin" in result assert "Dest" in result assert "13:05" not in result # arrive time should not appear def test_flight_arrive_is_none() -> None: """Flight where 'arrive' key exists but value is None must render cleanly.""" element = _flight( { "depart": datetime(2023, 7, 1, 13, 20, tzinfo=timezone.utc), "arrive": None, "from": "TIA", "to": "LHR", "flight_number": "BA123", "airline_code": "BA", "airline_name": "British Airways", "distance": 2000.0, } ) result = _render(element) assert "Origin" in result def test_train_date_only_fields() -> None: """Train with date (not datetime) depart/arrive must not raise AttributeError. Some older train entries record only the travel date with no time. datetime.date has no .date() method, so item.depart.date() would fail. """ element = _train( { "depart": date(2024, 10, 22), "arrive": date(2024, 10, 22), "from": "Llandrindod", "to": "Llandovery", "operator": "Transport for Wales", } ) result = _render(element) assert "Llandrindod" in result assert "Llandovery" in result def test_train_overnight_datetime() -> None: """Overnight train (arrive on next day) shows night moon emoji and +1 day.""" tz = timezone.utc element = _train( { "depart": datetime(2026, 1, 19, 19, 6, tzinfo=tz), "arrive": datetime(2026, 1, 20, 10, 13, tzinfo=tz), "from": "Brussels Midi", "to": "Vienna Hbf", "operator": "ÖBB", } ) result = _render(element) assert "🌙" in result assert "+1 day" in result def test_train_seat_as_integer() -> None: """Train with seat as an integer (not a list) must render without error.""" tz = timezone.utc element = _train( { "depart": datetime(2025, 5, 26, 13, 5, tzinfo=tz), "arrive": datetime(2025, 5, 26, 13, 29, tzinfo=tz), "from": "Wánchaq", "to": "Puno", "coach": "C", "seat": 1, } ) result = _render(element) assert "Wánchaq" in result # --------------------------------------------------------------------------- # Integration tests — render the real trip list pages end-to-end # --------------------------------------------------------------------------- @pytest.fixture def client(): """Flask test client.""" web_view.app.config["TESTING"] = True with web_view.app.test_client() as c: yield c def test_past_trips_page_renders(client) -> None: """Past trips list page renders without any template errors (HTTP 200).""" response = client.get("/trip/past") assert response.status_code == 200 def test_future_trips_page_renders(client) -> None: """Future trips list page renders without any template errors (HTTP 200).""" response = client.get("/trip/future") assert response.status_code == 200