"""The destinations serialiser's contract. Not rendering a figure is not the same as not publishing it. This endpoint is public and unauthenticated, so whatever the payload carries is published, whatever the UI draws. The categories sum to the cohort and the pupil groups sum to each other, so a lone suppressed cell is solvable by subtraction — the serialiser adds secondary suppression to prevent it. See docs/superpowers/specs/2026-08-28-destination-measures-design.md. """ from backend.data_loader import _destinations_block, _format_cohort_year def _row(group, measure, pupils, status, cohort=180, percentage=None, year=202223): return { "pupil_group": group, "destination_measure": measure, "pupils": pupils, "percentage": percentage, "status": status, "cohort_pupils": cohort, "year": year, } def test_suppressed_category_serialises_as_suppressed_with_null_pupils(): rows = [ _row("all", "school_sixth_form", 75, "published", percentage=41.7), _row("all", "sixth_form_college", None, "suppressed"), ] block = _destinations_block(rows) cats = {c["category"]: c for c in block["groups"]["all"]["categories"]} assert cats["sixth_form_college"]["status"] == "suppressed" assert cats["sixth_form_college"]["pupils"] is None assert cats["sixth_form_college"]["percentage"] is None def test_published_category_keeps_its_figures(): block = _destinations_block([ _row("all", "school_sixth_form", 75, "published", percentage=41.7), ]) cat = block["groups"]["all"]["categories"][0] assert cat["pupils"] == 75 assert cat["percentage"] == 41.7 assert cat["status"] == "published" def test_only_the_latest_year_is_served(): rows = [ _row("all", "school_sixth_form", 60, "published", year=202122), _row("all", "school_sixth_form", 75, "published", year=202223), ] block = _destinations_block(rows) assert block["cohort_year"] == "2022/23" assert len(block["groups"]["all"]["categories"]) == 1 assert block["groups"]["all"]["categories"][0]["pupils"] == 75 def test_all_three_pupil_groups_are_carried(): rows = [ _row("all", "school_sixth_form", 75, "published"), _row("disadvantaged", "school_sixth_form", 17, "published", cohort=62), _row("other", "school_sixth_form", 58, "published", cohort=118), ] block = _destinations_block(rows) assert set(block["groups"]) == {"all", "disadvantaged", "other"} assert block["groups"]["disadvantaged"]["cohort"] == 62 def test_cohort_year_is_reported_so_the_page_can_date_itself(): block = _destinations_block([_row("all", "school_sixth_form", 75, "published")]) assert block["cohort_year"] == "2022/23" def test_format_cohort_year_handles_the_six_digit_form(): assert _format_cohort_year(202223) == "2022/23" assert _format_cohort_year(None) is None def test_empty_rows_yield_none_not_an_empty_shell(): assert _destinations_block([]) is None # ── Disclosure control ────────────────────────────────────────────────────── # # The rendering guards in lib/destinations.ts stop a withheld figure being # DRAWN. They do nothing about it being COMPUTED: this endpoint is public and # unauthenticated, so whatever the payload carries is published. These tests # are the ones that matter. def _solve_residual(group): """What any caller can work out: cohort minus everything published.""" published = [c["pupils"] for c in group["categories"] if c["pupils"] is not None] hidden = [c for c in group["categories"] if c["status"] == "suppressed"] return group["cohort"] - sum(published), len(hidden) def test_a_lone_suppressed_category_cannot_be_solved_for(): """Whitley Bay High School's real 2022/23 disadvantaged group: further education withheld, everything else published, cohort 41. Before secondary suppression the payload gave the answer away as 41 - 23 = 18.""" rows = [ _row("disadvantaged", "school_sixth_form", 15, "published", cohort=41), _row("disadvantaged", "sixth_form_college", 0, "published", cohort=41), _row("disadvantaged", "further_education", None, "suppressed", cohort=41), _row("disadvantaged", "apprenticeship", 1, "published", cohort=41), _row("disadvantaged", "employment", 2, "published", cohort=41), _row("disadvantaged", "not_sustained", 3, "published", cohort=41), _row("disadvantaged", "not_captured", 2, "published", cohort=41), ] group = _destinations_block(rows)["groups"]["disadvantaged"] residual, hidden = _solve_residual(group) assert hidden >= 2, "a lone suppressed cell must gain a companion" assert residual != 18, "the withheld figure is recoverable from the payload" def test_every_group_hides_none_or_at_least_two_categories(): rows = [ _row("all", "school_sixth_form", 75, "published"), _row("all", "sixth_form_college", None, "suppressed"), _row("all", "further_education", 61, "published"), _row("all", "apprenticeship", 8, "published"), _row("all", "employment", 6, "published"), _row("all", "not_sustained", 5, "published"), _row("all", "not_captured", 4, "published"), ] group = _destinations_block(rows)["groups"]["all"] hidden = [c for c in group["categories"] if c["status"] == "suppressed"] assert len(hidden) >= 2 def test_a_category_hidden_in_one_group_is_hidden_in_a_second(): """disadvantaged + other = all for every category, so a category withheld in exactly one of the three is recoverable from the other two.""" rows = [] for measure, a, d, o in [ ("school_sixth_form", 75, None, 58), ("further_education", 61, 27, 34), ("apprenticeship", 8, 4, 4), ("employment", 6, 1, 5), ("not_sustained", 5, 3, 2), ("not_captured", 4, 2, 2), ]: rows.append(_row("all", measure, a, "published", cohort=159)) rows.append(_row("disadvantaged", measure, d, "published" if d is not None else "suppressed", cohort=37)) rows.append(_row("other", measure, o, "published", cohort=122)) groups = _destinations_block(rows)["groups"] for measure in ["school_sixth_form"]: hidden = sum( 1 for key in ("all", "disadvantaged", "other") for c in groups[key]["categories"] if c["category"] == measure and c["status"] == "suppressed" ) assert hidden >= 2, f"{measure} is solvable across the pupil groups" def test_a_suppressed_cell_never_keeps_its_percentage(): """percentage / pupils would hand back the cohort, and with it the residual.""" rows = [ _row("all", "school_sixth_form", 75, "published", percentage=41.7), _row("all", "sixth_form_college", None, "suppressed", percentage=11.7), _row("all", "further_education", 61, "published", percentage=33.9), ] group = _destinations_block(rows)["groups"]["all"] for cell in group["categories"]: if cell["status"] != "published": assert cell["pupils"] is None assert cell["percentage"] is None def test_aggregates_are_not_served(): """An aggregate spanning exactly one suppressed component names it, and nothing renders them today.""" rows = [ _row("all", "school_sixth_form", 75, "published"), _row("all", "agg_sustained_all", 171, "published"), ] group = _destinations_block(rows)["groups"]["all"] assert [c["category"] for c in group["categories"]] == ["school_sixth_form"] assert "aggregates" not in group def test_a_fully_published_group_is_left_alone(): """Secondary suppression must not cost anything where nothing is withheld — this is the all-pupils view on every mainstream secondary.""" rows = [ _row("all", m, p, "published") for m, p in [("school_sixth_form", 75), ("sixth_form_college", 21), ("further_education", 61), ("apprenticeship", 8), ("employment", 6), ("not_sustained", 5), ("not_captured", 4)] ] group = _destinations_block(rows)["groups"]["all"] assert all(c["status"] == "published" for c in group["categories"]) assert len(group["categories"]) == 7