spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
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®ion=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