"""Unit tier: the SSR rendering layer (content.py + format.py) fed committed backend fixtures via the hermetic `client` fixture (tests/conftest.py). Asserts the rendering *contract* — pages render and carry the expected structure — not exact climate numbers (those are the backend's responsibility). Ported from the old backend-coupled test_content.py; refresh the fixtures with scripts/capture-fixtures.sh. """ import re import pytest B = "/thermograph" # matches THERMOGRAPH_BASE in tests/conftest.py SLUG = "london-england-gb" MONTH = "july" def test_robots_txt(client): r = client.get(f"{B}/robots.txt") assert r.status_code == 200 assert "Sitemap:" in r.text and "/sitemap.xml" in r.text assert f"Disallow: {B}/api/" in r.text def test_sitemap_lists_city_urls(client): r = client.get(f"{B}/sitemap.xml") assert r.status_code == 200 assert "" in r.text assert f"/climate/{SLUG}/{MONTH}" in r.text assert f"/climate/{SLUG}/records" in r.text # The public site is HTTPS-only behind Caddy, which terminates TLS and proxies # plain HTTP -- so x-forwarded-proto / request.url.scheme read "http". A request # arriving on the configured public host (THERMOGRAPH_BASE_URL defaults to # https://thermograph.org in the test env) must still emit https:// in every # frontend-built absolute URL, or canonicals self-conflict and the sitemap lists # redirecting URLs. _PUBLIC = {"host": "thermograph.org", "x-forwarded-proto": "http"} def test_public_host_canonical_and_og_are_https(client): b = client.get(f"{B}/climate/{SLUG}", headers=_PUBLIC).text assert f'https://thermograph.org{B}/climate/{SLUG}" in body assert "http://thermograph.org" not in body def test_public_host_robots_sitemap_is_https(client): body = client.get(f"{B}/robots.txt", headers=_PUBLIC).text assert f"Sitemap: https://thermograph.org{B}/sitemap.xml" in body def test_non_public_host_keeps_forwarded_scheme(client): # A LAN/dev host never matches the configured public host, so the observed # (plain-http) scheme is preserved -- http development is unaffected. b = client.get(f"{B}/climate/{SLUG}", headers={"host": "192.168.1.10:8000", "x-forwarded-proto": "http"}).text assert f'", b) # GB city -> Celsius @pytest.mark.parametrize("path", ["", f"/{MONTH}", "/records"]) def test_city_subpages_render(client, path): r = client.get(f"{B}/climate/{SLUG}{path}") assert r.status_code == 200 assert "text/html" in r.headers["content-type"] assert "__ORIGIN__" not in r.text # origin placeholder filled in def test_homepage_renders(client): r = client.get(f"{B}/") assert r.status_code == 200 assert "text/html" in r.headers["content-type"] def test_hub_renders(client): r = client.get(f"{B}/climate") assert r.status_code == 200 assert "text/html" in r.headers["content-type"] def test_unknown_city_is_404(client): assert client.get(f"{B}/climate/nope-not-a-city").status_code == 404 def test_unknown_month_is_404(client): assert client.get(f"{B}/climate/{SLUG}/nonemonth").status_code == 404 # --- backend-down resilience (migrated from main's hardening of the old # tests/test_content.py, which the unit-tier restructure deleted) ------------ def _unreachable(*_a, **_k): import httpx req = httpx.Request("GET", "http://backend.invalid/x") raise httpx.ConnectError("connection refused", request=req) @pytest.mark.parametrize("path, attr", [ (f"/climate/{SLUG}", "city"), (f"/climate/{SLUG}/july", "city_month"), (f"/climate/{SLUG}/records", "city_records"), ("/climate", "hub"), ("/", "home"), ("/sitemap.xml", "sitemap"), ]) def test_backend_unreachable_maps_to_503(client, monkeypatch, path, attr): """A backend that's down/timing out (httpx.ConnectError, TimeoutException, ...) never raises httpx.HTTPStatusError -- it doesn't even get a response -- so it must be caught separately from the 404/503 status-code passthrough or it escapes as a raw framework 500 instead of a clean 503.""" import api_client monkeypatch.setattr(api_client, attr, _unreachable) assert client.get(f"{B}{path}").status_code == 503