SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
12.4 KB · 208 lines python
Raw Blame History
1"""Analytics endpoints (API 1.1) on the synthetic fixture (8 countries × 14 indicators, 1990–2024)."""2from __future__ import annotations34import pytest56from tests.api.conftest import assert_meta, assert_provenance, value789def test_pulse(get):10    body = get("/pulse").json()11    assert_meta(body)12    assert body["year_reference"] == 202413    # fixture: 8 countries only, below the 30-country pool threshold → no items but a valid envelope14    assert body["items"] == [] and body["summary"]["n_countries_reporting"] == 015    assert "n_record_highs" in body["summary"]161718def test_movers_window_1_and_kinds(get):19    body = get("/movers?window=1&limit=10").json()20    assert body["window"] == 1 and body["categories"][0] == "all" and "structural" in body["kinds"]21    assert body["n"] == len(body["items"]) > 022    it = body["items"][0]23    for k in ("country", "indicator", "kind", "value", "delta", "direction", "interpretation", "headline", "provenance"):24        assert k in it25    assert it["direction"] in ("up", "down")26    assert_provenance(it["provenance"])27    rec = get("/movers?window=1&kind=record&limit=50").json()28    assert rec["items"] and all(i["kind"] in ("record_high", "record_low") for i in rec["items"])29    imp = get("/movers?window=1&kind=improvement&limit=50").json()30    assert all(i["interpretation"] == "improvement" for i in imp["items"])31    econ = get("/movers?window=1&category=economic&limit=50").json()32    assert all(i["indicator"]["topic"] in ("economy", "government", "trade", "income") for i in econ["items"])33    get("/movers?window=3", status=400)34    get("/movers?category=weird", status=400)353637def test_movers_multi_year_window_needs_pool(get):38    body = get("/movers?window=10&limit=10").json()  # 8 countries < 20 → empty but valid39    assert body["window"] == 10 and body["items"] == []40    body = get("/movers?window=5&kind=record").json()41    assert body["items"] == []424344def test_extremes_envelope(get):45    body = get("/extremes?window=10").json()46    assert body["window"] == "10" and body["filter_note"] == "Countries above 1M inhabitants"47    assert body["facets"] == []  # every facet needs ≥ 30 countries in the window48    get("/extremes?window=7", status=400)495051def test_scatter(get):52    body = get("/scatter?x=gdp-per-capita&y=life-expectancy&year=2024&log_x=true").json()53    assert_meta(body)54    assert body["year_used"] == 2024 and body["n"] == 8 and body["size"]["id"] == "population"55    p = next(pt for pt in body["points"] if pt["id"] == "CAN")56    assert p["x"] == pytest.approx(value("CAN", "gdp-per-capita", 2024)) and p["y"] == pytest.approx(value("CAN", "life-expectancy", 2024))57    assert p["size"] == pytest.approx(value("CAN", "population", 2024)) and p["year_x"] == 202458    st = body["stats"]59    assert st["n"] == 8 and -1 <= st["pearson"] <= 1 and -1 <= st["spearman"] <= 1 and st["ols"]["slope"] is not None60    assert st["log_x"] is True and st["log_y"] is False61    assert "causation" in body["note"]62    none = get("/scatter?x=gdp-per-capita&y=life-expectancy&size=none").json()63    assert none["size"] is None and none["stats"]["log_x"] is False and none["year_used"] == 2024  # auto: ratio max/min < 50 in the fixture64    get("/scatter?x=gdp&y=gdp", status=400)65    get("/scatter?x=gdp&y=nope", status=404)66    grp = get("/scatter?x=gdp-per-capita&y=life-expectancy&group=g7").json()67    assert grp["group"]["id"] == "g7" and grp["n"] == 5686970def test_trajectory(get):71    body = get("/trajectory?x=gdp-per-capita&y=life-expectancy&from=2000&to=2005").json()72    assert body["years"] == [2000, 2001, 2002, 2003, 2004, 2005]73    assert len(body["countries"]) == 8 and set(body["series"]) == {c["id"] for c in body["countries"]}74    s = body["series"]["IND"]75    assert len(s["x"]) == 6 and s["x"][0] == pytest.approx(value("IND", "gdp-per-capita", 2000))76    assert s["size"][0] == pytest.approx(value("IND", "population", 2000))77    assert body["domains"]["x"][0] <= min(v for v in s["x"]) and body["log_x"] is True  # pooled x spans > 50× → log78    assert body["provenance"]79    auto = get("/trajectory?x=gdp-per-capita&y=life-expectancy").json()80    assert auto["years"][0] == 1990 and auto["years"][-1] == 2024  # < 50 countries → every year with data81    get("/trajectory?x=gdp&y=gdp", status=400)828384def test_finder(get):85    body = get("/finder?f=gdp-per-capita:gt:40000&f=population:gt:100000000").json()86    assert body["mode"] == "and" and [f["indicator"]["id"] for f in body["filters"]] == ["gdp-per-capita", "population"]87    assert body["filters"][0]["op"] == "gt" and body["filters"][0]["value"] == 40000 and body["filters"][0]["year_used"] == 202488    ids = [i["country"]["id"] for i in body["items"]]89    assert ids == ["JPN", "USA"]  # rich (2024 gpc > 40k) and populous; sorted by gpc desc (JPN 69k > USA 65k in the fixture)90    assert body["n_matching"] == 2 and body["n_evaluated"] == 891    assert body["items"][0]["matched"] == ["gdp-per-capita", "population"]92    assert_provenance(body["items"][0]["values"]["gdp-per-capita"]["provenance"])93    any_ = get("/finder?f=gdp-per-capita:gt:40000&f=population:gt:1000000000&mode=or").json()94    assert "IND" in [i["country"]["id"] for i in any_["items"]]  # India by population only95    between = get("/finder?f=life-expectancy:between:60..70&sort=population:asc").json()96    assert all(60 <= i["values"]["life-expectancy"]["value"] <= 70 for i in between["items"])97    reg = get("/finder?f=population:gt:1&region=g7").json()98    assert reg["n_matching"] == 599    get("/finder", status=400)100    get("/finder?f=gdp-per-capita:huge:1", status=400)101    get("/finder?f=nope:gt:1", status=404)102103104def test_peers(get):105    body = get("/peers?y=life-expectancy&x=gdp-per-capita&log_x=true").json()106    assert body["year_used"] == 2024 and body["n"] == 8 and body["method"] == "theil-sen"107    assert body["fit"]["log_x"] is True and body["fit"]["residual_scale"] is not None108    pts = {p["id"]: p for p in body["points"]}109    assert pts["CAN"]["expected"] is not None and pts["CAN"]["residual"] == pytest.approx(pts["CAN"]["y"] - pts["CAN"]["expected"])110    assert body["above"][0]["residual_z"] >= body["above"][-1]["residual_z"] and body["below"][0]["residual_z"] <= body["below"][-1]["residual_z"]111    assert body["pairs"] and "descriptive" in body["note"]112    ols = get("/peers?y=life-expectancy&x=gdp-per-capita&method=ols").json()113    assert ols["fit"]["r2"] is not None114    get("/peers?y=gdp&x=gdp", status=400)115116117def test_related_distribution_frames(get):118    rel = get("/indicators/life-expectancy/related?min_n=5").json()119    assert rel["indicator"]["id"] == "life-expectancy" and rel["items"] and "causation" in rel["note"]120    assert all(i["indicator"]["id"] != "life-expectancy" for i in rel["items"])121    assert abs(rel["items"][0]["spearman"]) >= abs(rel["items"][-1]["spearman"])122    assert get("/indicators/life-expectancy/related").json()["items"] == []  # min_n 40 > 8 countries123124    d = get("/indicators/life-expectancy/distribution?highlight=JPN").json()125    assert d["year_used"] == 2024 and d["n"] == 8 and sum(d["histogram"]["counts"]) == 8 and len(d["histogram"]["edges"]) == 21126    assert d["stats"]["min"] <= d["stats"]["median"] <= d["stats"]["max"]127    assert d["highlight"]["country"]["id"] == "JPN" and d["highlight"]["rank"] == 1 and d["highlight"]["percentile"] == pytest.approx(100.0)128    assert d["by_income"] and d["by_region"]129    assert_provenance(d["provenance"])130    lg = get("/indicators/gdp-per-capita/distribution?bins=5").json()131    assert lg["log"] is False and len(lg["histogram"]["counts"]) == 5  # auto-log needs a > 50× spread; the fixture's 2024 spread is ~25×132133    fr = get("/indicators/gdp-per-capita/frames?from=2000&to=2003").json()134    assert fr["years"] == [] or fr["years"][0] >= 2000  # fixture has 8 countries (< 20 per year) → no frames135    assert fr["legend"]["n_classes"] >= 1 and isinstance(fr["values"], dict)136137138def test_quality_and_updates(get):139    q = get("/indicators/life-expectancy/quality").json()140    assert q["n_countries"] == 8 and q["n_countries_total"] == 8 and q["coverage_pct"] == 100.0141    assert q["first_year"] == 1990 and q["last_year"] == 2024 and q["years_with_50plus"] == 0142    assert "fresh" in q["badges"] and "historical" not in q["badges"] and q["sources"]143    get("/indicators/nope/quality", status=404)144    cq = get("/countries/canada/quality").json()145    assert cq["country"]["id"] == "CAN" and cq["summary"]["n_with_data"] == 14 and cq["summary"]["latest_year"] == 2024146    it = next(i for i in cq["items"] if i["indicator"]["id"] == "government-debt-pct-gdp")147    assert it["first_year"] == 1995 and it["n_points"] == 30 and it["missing_years"] == 0 and it["continuity_pct"] == 100.0148    assert "fresh" in it["badges"]149    u = get("/updates").json()150    assert_meta(u)151    assert u["snapshot"]["observations"] > 0 and u["snapshot"]["values_changed"] == 0152    ids = {s["source"]["id"]: s for s in u["sources"]}153    assert ids["worldbank"]["status"] == "ok" and ids["who"]["status"] == "failed" and ids["owid"]["status"] == "partial"154    assert ids["worldbank"]["n_observations"] > 0 and ids["worldbank"]["latest_year"] == 2024155    assert len(u["recent_runs"]) == 6 and all("raw_path" not in r for r in u["recent_runs"])156    assert u["indicators_recently_updated"]157158159def test_race_and_regions_compare(get):160    r = get("/rankings/gdp/race?top=3&from=2020").json()161    assert r["years"] == [] or r["frames"][0]["year"] >= 2020  # fixture rankings have 8 countries (< 20) → no frames162    assert r["top"] == 3 and r["indicator"]["id"] == "gdp" and r["group"]["id"] == "world"163    get("/rankings/nope/race", status=404)164    rc = get("/regions/compare?a=g7&b=brics").json()165    assert [g["id"] for g in rc["groups"]] == ["g7", "brics"]166    row = next(x for x in rc["rows"] if x["indicator"]["id"] == "population")167    assert row["kind"] == "sum" and row["values"]["g7"]["n"] == 5 and row["values"]["brics"]["n"] == 2168    assert rc["shares"]["g7"]["population_share_pct"] + rc["shares"]["brics"]["population_share_pct"] < 100.01169    h = rc["history"]["gdp-per-capita"]170    assert h["kind"] == "weighted_mean" and h["years"][0] == 1990 and len(h["g7"]) == len(h["years"])171    get("/regions/compare?a=nope&b=g7", status=404)172173174def test_story_and_dna_reference(get):175    s = get("/countries/canada/story").json()176    assert s["country"]["id"] == "CAN" and s["since"] == 1990 and s["items"]177    pop = next(i for i in s["items"] if i["indicator"]["id"] == "population")178    assert pop["first"]["year"] == 1990 and pop["last"]["year"] == 2024 and pop["change_pct"] > 0 and pop["cagr"] == pytest.approx(1.0, abs=0.05)179    assert pop["rank_first"]["year"] == 1990 and pop["rank_last"]["rank"] >= 1 and len(pop["series"]) == 35180    assert pop["text"].startswith("Canada: Population") and "1990" in pop["text"] and "2024" in pop["text"]181    assert_provenance(pop["provenance"])182    le = next(i for i in s["items"] if i["indicator"]["id"] == "life-expectancy")183    assert le["cagr"] is None and "yrs" in le["text"]184    d = get("/countries/canada/dna?reference=world").json()185    assert d["reference"]["kind"] == "world" and set(d["reference"]["dims"].values()) == {50.0}186    d = get("/countries/canada/dna?reference=income").json()187    assert d["reference"]["kind"] == "income" and d["reference"]["id"] == "hic" and d["reference"]["dims"]["income"] is not None188    d = get("/countries/canada/dna?reference=fra").json()189    assert d["reference"]["kind"] == "country" and d["reference"]["id"] == "FRA"190    assert get("/countries/canada/dna").json()["reference"] is None191    get("/countries/canada/dna?reference=xxx", status=404)192193194def test_search_intents(get):195    hits = get("/search?q=compare%20canada%20usa").json()["hits"]196    assert hits[0]["type"] == "action" and hits[0]["action"] == "compare" and hits[0]["url"] == "/compare/canada/united-states"197    hits = get("/search?q=canada%20vs%20france%20vs%20japan").json()["hits"]198    assert hits[0]["url"] == "/compare/canada/france/japan"199    hits = get("/search?q=rank%20gdp").json()["hits"]200    assert hits[0]["action"] == "ranking" and hits[0]["url"] == "/rankings/gdp"201    hits = get("/search?q=inflation%20g7").json()["hits"]202    assert hits[0]["action"] == "group_ranking" and hits[0]["url"] == "/rankings/inflation?group=g7"203    hits = get("/search?q=life%20expectancy%20map").json()["hits"]204    assert hits[0]["action"] == "explore" and hits[0]["url"] == "/explore?indicator=life-expectancy"205    # plain queries are unchanged206    assert get("/search?q=canada").json()["hits"][0]["type"] == "country"207    assert all(h["type"] != "action" for h in get("/search?q=gdp&type=indicator").json()["hits"])208