schiphol/tests/test_poll.py
Peter 47b8fd29f8 test: add pytest infrastructure and poll() tests
- Add pytest and pytest-asyncio as optional dev dependencies
- Configure asyncio_mode=auto and pythonpath=[src] in pyproject.toml
- Write three tests at the poll() seam:
  * single-page response publishes all Flights and returns the count
  * paginated response follows link:rel=next and publishes all pages
  * HTTP error from raise_for_status() propagates out of poll()

Tests use injected stub HTTP session and mock Kafka engine;
no real network or broker required.

Closes #4

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-19 09:07:27 +02:00

122 lines
3.5 KiB
Python

"""Tests for poll() — the Schiphol API fetch-and-publish sweep."""
import pytest
import requests
from main import poll
# ---------------------------------------------------------------------------
# Test doubles
# ---------------------------------------------------------------------------
class _NoOpSpan:
def __enter__(self):
return self
def __exit__(self, *args):
pass
class StubTracer:
def start_as_current_span(self, name, attributes=None):
return _NoOpSpan()
class StubResponse:
"""Minimal requests.Response lookalike."""
def __init__(self, flights, *, next_url=None, raise_on_status=False):
self._flights = flights
self._raise = raise_on_status
self.headers = {}
if next_url:
self.headers["link"] = f'<{next_url}>; rel="next"'
def raise_for_status(self):
if self._raise:
raise requests.HTTPError("500 Server Error")
def json(self):
return {"flights": self._flights}
class StubSession:
"""Returns pre-configured StubResponse objects keyed by URL."""
def __init__(self, responses_by_url: dict):
self._responses = responses_by_url
def get(self, url):
return self._responses[url]
class MockEngine:
"""Captures every send() call so tests can assert on Kafka output."""
def __init__(self):
self.sent: list[dict] = []
async def send(self, topic, *, value, key):
self.sent.append({"topic": topic, "value": value, "key": key})
class FakeApiSettings:
base_url = "https://api.example.com/flights"
class FakeKafkaSettings:
topic = "flights-test"
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
async def test_single_page_publishes_all_flights_and_returns_count():
"""poll() publishes every Flight on a single-page response and returns the count."""
flights = [{"id": "1", "flightName": "KL100"}, {"id": "2", "flightName": "KL200"}]
session = StubSession({FakeApiSettings.base_url: StubResponse(flights)})
engine = MockEngine()
count = await poll(session, engine, FakeApiSettings(), FakeKafkaSettings(), StubTracer())
assert count == 2
assert len(engine.sent) == 2
published_keys = {msg["key"] for msg in engine.sent}
assert published_keys == {"1", "2"}
async def test_pagination_publishes_flights_across_all_pages():
"""poll() follows link: rel="next" headers and publishes Flights from every page."""
page1_url = FakeApiSettings.base_url
page2_url = "https://api.example.com/flights?page=2"
page1_flights = [{"id": "A"}, {"id": "B"}]
page2_flights = [{"id": "C"}, {"id": "D"}, {"id": "E"}]
session = StubSession(
{
page1_url: StubResponse(page1_flights, next_url=page2_url),
page2_url: StubResponse(page2_flights),
}
)
engine = MockEngine()
count = await poll(session, engine, FakeApiSettings(), FakeKafkaSettings(), StubTracer())
assert count == 5
assert len(engine.sent) == 5
async def test_http_error_propagates_out_of_poll():
"""poll() lets HTTP errors from raise_for_status() propagate to the caller."""
session = StubSession(
{FakeApiSettings.base_url: StubResponse([], raise_on_status=True)}
)
engine = MockEngine()
with pytest.raises(requests.HTTPError):
await poll(session, engine, FakeApiSettings(), FakeKafkaSettings(), StubTracer())