- 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>
122 lines
3.5 KiB
Python
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())
|