diff --git a/pyproject.toml b/pyproject.toml index f2e2244..6137045 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,4 +28,8 @@ build-backend = "hatchling.build" [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "-q" +# Deselect live API tests by default; run them with `pytest -m e2e`. +addopts = "-q -m 'not e2e'" +markers = [ + "e2e: live tests that call the real Google Routes API (opt-in, costs quota)", +] diff --git a/tests/test_e2e_api.py b/tests/test_e2e_api.py new file mode 100644 index 0000000..4f0c9e4 --- /dev/null +++ b/tests/test_e2e_api.py @@ -0,0 +1,67 @@ +"""Live end-to-end test against the real Google Routes API. + +This is intentionally excluded from the default test run (it is marked ``e2e`` +and ``addopts`` in pyproject.toml deselects that marker). It also costs billable +API elements, so it is kept minimal: two addresses -> a 2x2 (4-element) matrix. + +Run it explicitly when you actually want to exercise the live API: + + uv run pytest -m e2e + +It requires a real GOOGLE_MAPS_API_KEY (read from the environment or the local +.env file); the test skips itself if only a placeholder/dummy key is available. +""" + +import os + +import pandas as pd +import pytest +from dotenv import load_dotenv + +import tatami.traveltimes as tt +from tatami.classes import Participant + +pytestmark = pytest.mark.e2e + +# Values that are not a usable real key (conftest's dummy and the template value). +_PLACEHOLDER_KEYS = {"", "test-key", "your-api-key-here"} + + +def _real_api_key() -> str | None: + # Prefer a real value from the environment / .env over the dummy that conftest + # sets for the rest of the suite. + load_dotenv(override=True) + key = os.environ.get("GOOGLE_MAPS_API_KEY", "") + return key if key not in _PLACEHOLDER_KEYS else None + + +@pytest.fixture +def live_api(monkeypatch): + key = _real_api_key() + if key is None: + pytest.skip("No real GOOGLE_MAPS_API_KEY available; skipping live API test.") + # traveltimes captured the key at import time, so point the module global at + # the real key for this test. + monkeypatch.setattr(tt, "GOOGLE_MAPS_API_KEY", key) + return key + + +def test_live_route_matrix_minimal(live_api): + # Two real addresses -> a 2x2 matrix (4 billable elements), the smallest + # meaningful request. + participants = [ + Participant("A", "Römerstr. 12, 76189 Karlsruhe", "", 10, ""), + Participant("B", "Gottesauerstr. 30, 76131 Karlsruhe", "", 10, ""), + ] + + matrix = tt.get_participant_distance_matrix(participants, mode="BICYCLE") + + uuids = [p.uuid for p in participants] + assert matrix.shape == (2, 2) + assert list(matrix.index) == uuids + assert list(matrix.columns) == uuids + + # A real bike trip between two distinct addresses takes a positive duration. + travel = matrix.loc[uuids[0], uuids[1]] + assert isinstance(travel, pd.Timedelta) + assert travel > pd.Timedelta(0)