"""Regression tests for GET /api/schools/{urn}. Schools with no performance rows (special post-16 institutions, sixth-form centres, PRUs, brand-new schools) come back from the marts LEFT JOIN with NaN in every numeric column. The endpoint must still serialize them — a NaN that reaches Starlette's JSONResponse raises ValueError (allow_nan=False) and the route 500s, which the frontend then renders as a 404. """ import numpy as np import pandas as pd import pytest from fastapi.testclient import TestClient def _no_results_school_df() -> pd.DataFrame: """One school row as produced by the marts query for a school with no performance data: GIAS/location fields partly populated, every results-linked column NaN (including year).""" return pd.DataFrame( [ { "urn": 150275, "school_name": "West London Performing Arts Academy", "phase": "Secondary", "school_type": "Special post 16 institution", "trust_name": None, "religious_denomination": "Does not apply", "gender": None, "age_range": "16-25", "admissions_policy": None, "capacity": np.nan, "gias_total_pupils": np.nan, "headteacher_name": None, "website": None, "ofsted_grade": np.nan, "local_authority": "Ealing", "address": "268 Northfield Avenue, London, W5 4UB", "postcode": "W5 4UB", "latitude": 51.4986, "longitude": -0.3148, "year": np.nan, "total_pupils": np.nan, "eligible_pupils": np.nan, "rwm_expected_pct": np.nan, } ] ) @pytest.fixture() def client(monkeypatch): from backend import app as app_module monkeypatch.setattr(app_module, "load_school_data", _no_results_school_df) monkeypatch.setattr( app_module, "get_supplementary_data", lambda db, urn: {} ) return TestClient(app_module.app, raise_server_exceptions=False) def test_school_without_performance_rows_returns_200(client): resp = client.get("/api/schools/150275") assert resp.status_code == 200, resp.text def test_nan_gias_fields_serialize_as_null(client): info = client.get("/api/schools/150275").json()["school_info"] assert info["capacity"] is None assert info["total_pupils"] is None assert info["school_name"] == "West London Performing Arts Academy" # ── Links out to the location layer ───────────────────────────────────────── # # School pages carried no link into the site at all: the only anchor on the # template pointed at the school's own website, so ~27k pages received # whatever authority the site had and sent it off-site. `places` is what the # link module and the breadcrumb are built from. def test_places_is_present_even_when_the_school_belongs_to_none(client): # This fixture's single school cannot clear any publish threshold, so the # honest answer is an empty list. The key must still be there: a missing # key and "no places" are different things to the page rendering it. body = client.get("/api/schools/150275").json() assert body["places"] == [] def test_places_names_only_pages_that_exist(monkeypatch): from backend import app as app_module from backend.places import MIN_SCHOOLS def _df(): return pd.DataFrame([ { "urn": 100000 + i, "school_name": f"Brentwood School {i}", "town": "Brentwood", "local_authority": "Essex", "postcode": "CM15 8AA", "phase": "Primary", "year": 202425, "rwm_expected_pct": 60.0, "attainment_8_score": np.nan, "ofsted_grade": 2.0, "ofsted_date": None, } for i in range(MIN_SCHOOLS) ]) monkeypatch.setattr(app_module, "load_school_data", _df) monkeypatch.setattr(app_module, "get_supplementary_data", lambda db, urn: {}) monkeypatch.setattr(app_module, "_place_registry", None) client = TestClient(app_module.app, raise_server_exceptions=False) places = client.get("/api/schools/100000").json()["places"] assert places, "a school in a published town must offer links" by_kind = {p["kind"]: p for p in places} assert by_kind["town"]["url"] == "/schools/brentwood" assert by_kind["authority"]["url"] == "/schools/authority/essex" # Every entry carries what the link text needs, and a count, so the anchor # can say what it leads to rather than "click here". for place in places: assert place["name"] assert place["count"] >= 1 assert place["url"].startswith("/schools/")