"""Integration tier: run the SSR app against a REAL backend container (pulled by scripts/backend-for-tests.sh). Exercises the actual HTTP contract instead of backend source — catches drift the unit fixtures can't. Asserts the contract (reachability, version negotiation, payload shape, SSR renders 200), not exact climate numbers. Skips automatically when Docker/the image isn't available (see tests/conftest.py). """ import json import os import httpx import pytest B = "/thermograph" SLUG = "london-england-gb" MONTH = "july" API = "v2" # frontend api_client.API_VERSION _FIXTURES = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "fixtures") def _live(backend_service, path): # The backend container runs THERMOGRAPH_BASE=/, so its API is at /api//... return httpx.get(f"{backend_service}{path}", timeout=40.0) def test_backend_version_is_compatible(backend_service): """The live backend advertises a contract the frontend (API_VERSION=v2) can talk to.""" v = _live(backend_service, "/api/version").json() assert v["backend_version"] == "2", v # frontend targets /api/v2 assert int(v["min_frontend"]) <= 2, v # backend still accepts this frontend @pytest.mark.parametrize("ep", ["home", "sitemap", "hub", "city", "month", "records"]) def test_fixture_shapes_match_live_backend(backend_service, ep): """The committed unit fixtures still match the live payload shapes — proves the two tiers can't silently diverge. Compares top-level structure, not values.""" paths = { "home": f"/api/{API}/content/home", "sitemap": f"/api/{API}/content/sitemap", "hub": f"/api/{API}/content/hub", "city": f"/api/{API}/content/city/{SLUG}", "month": f"/api/{API}/content/city/{SLUG}/month/{MONTH}", "records": f"/api/{API}/content/city/{SLUG}/records", } live = _live(backend_service, paths[ep]) assert live.status_code == 200, live.text[:200] live_json = live.json() with open(os.path.join(_FIXTURES, f"{ep}.json"), encoding="utf-8") as fh: fixture = json.load(fh) if isinstance(fixture, dict): assert set(live_json.keys()) == set(fixture.keys()), ( f"{ep}: live keys {sorted(live_json)} != fixture keys {sorted(fixture)} " "— run scripts/capture-fixtures.sh") else: assert type(live_json) is type(fixture) def test_unknown_city_404(backend_service): assert _live(backend_service, f"/api/{API}/content/city/nope-not-a-city").status_code == 404 @pytest.mark.parametrize("path", ["/", "/climate", f"/climate/{SLUG}", f"/climate/{SLUG}/{MONTH}", f"/climate/{SLUG}/records", "/sitemap.xml", "/robots.txt"]) def test_ssr_renders_against_live_backend(live_client, path): """The SSR app rendering each page end-to-end against the live backend proves every field content.py reads is actually present in the real payload.""" r = live_client.get(f"{B}{path}") assert r.status_code == 200, f"{path} -> {r.status_code}" assert "__ORIGIN__" not in r.text def test_ssr_unknown_city_404(live_client): assert live_client.get(f"{B}/climate/nope-not-a-city").status_code == 404