spb/countryatlas
Public
1from __future__ import annotations23import csv4import io56import pytest78from tests.api.conftest import COUNTRIES, assert_meta, assert_provenance, value91011def test_list_countries(get):12 body = get("/countries").json()13 assert_meta(body)14 assert body["n"] == len(COUNTRIES)15 ids = [c["id"] for c in body["items"]]16 assert ids == sorted(ids, key=lambda i: next(c["name"] for c in body["items"] if c["id"] == i))17 can = next(c for c in body["items"] if c["id"] == "CAN")18 assert can["slug"] == "canada" and can["flag"] and can["region_name"] == "North America" and can["income"] == "HIC"19 assert can["population_latest"] == pytest.approx(value("CAN", "population", 2024))20 assert can["gdp_per_capita_latest"] == pytest.approx(value("CAN", "gdp-per-capita", 2024))21 assert can["coverage_pct"] == 100.0222324def test_list_countries_filters_and_sort(get):25 g7 = get("/countries?region=g7&sort=gdp").json()26 assert {c["id"] for c in g7["items"]} == {"CAN", "USA", "FRA", "DEU", "JPN"}27 vals = [c["gdp_latest"] for c in g7["items"]]28 assert vals == sorted(vals, reverse=True)29 assert get("/countries?region=north-america").json()["n"] == 230 assert {c["id"] for c in get("/countries?income=LMC").json()["items"]} == {"IND", "NGA"}31 assert get("/countries?q=jap").json()["items"][0]["id"] == "JPN"32 assert get("/countries?region=nowhere", status=404)33 assert get("/countries?sort=bogus", status=422)343536def test_country_overview(get):37 body = get("/countries/canada").json()38 assert_meta(body)39 c = body["country"]40 assert c["id"] == "CAN" and c["iso2"] == "CA" and c["capital"] == "Ottawa" and c["borders"] == ["USA"]41 assert "g7" in {g["id"] for g in body["groups"]} and "world" in {g["id"] for g in body["groups"]}42 assert body["coverage"]["n_indicators"] == 1443 assert body["freshness"]["source_updated_at"].startswith("2026-07-01") and body["freshness"]["built_at"]44 headline = {m["indicator"]: m for m in body["headline"]}45 assert list(headline) == ["population", "gdp", "gdp-per-capita", "gdp-growth", "inflation", "unemployment-rate", "life-expectancy",46 "median-age", "government-debt-pct-gdp", "co2-per-capita", "renewable-electricity-share", "internet-users"]47 gpc = headline["gdp-per-capita"]48 assert gpc["has_data"] and gpc["year"] == 2024 and gpc["value"] == pytest.approx(value("CAN", "gdp-per-capita", 2024))49 assert gpc["change"]["pct"] == pytest.approx(3.0) and gpc["change"]["formatted"] == "+3.0 %"50 assert gpc["rank_world"] and gpc["n_world"] == 8 and gpc["rank_region"] and gpc["n_region"] == 251 assert gpc["formatted"].endswith("k")52 assert len(gpc["sparkline"]) == 30 and gpc["sparkline"][-1][0] == 2024 and isinstance(gpc["sparkline"][-1][0], int)53 assert_provenance(gpc["provenance"])54 assert gpc["provenance"]["url"] == "https://data.worldbank.org/indicator/NY.GDP.PCAP.CD?locations=CA"55 assert gpc["provenance"]["series_code"] == "NY.GDP.PCAP.CD" and gpc["provenance"]["licence"] == "CC BY 4.0"56 # OWID provenance for co257 assert headline["co2-per-capita"]["provenance"]["url"] == "https://github.com/owid/co2-data"58 assert headline["median-age"]["provenance"]["url"] == "https://ourworldindata.org/grapher/median-age"59 topics = {t["id"]: t for t in body["topics"]}60 assert topics["economy"]["n_with_data"] >= 5 and topics["income"]["n_with_data"] == 061 assert [t["order"] for t in body["topics"]] == sorted(t["order"] for t in body["topics"])62 assert body["neighbours"][0]["id"] == "USA"636465def test_country_case_insensitive_and_iso2(get):66 assert get("/countries/can").json()["country"]["id"] == "CAN"67 assert get("/countries/CANADA").json()["country"]["id"] == "CAN"68 assert get("/countries/ca").json()["country"]["id"] == "CAN"697071def test_country_404_problem_json(client):72 r = client.get("/api/v1/countries/atlantis")73 assert r.status_code == 40474 assert r.headers["content-type"].startswith("application/problem+json")75 body = r.json()76 assert body["title"] == "Country not found" and body["status"] == 404 and "atlantis" in body["detail"]77 assert body["instance"] == "/api/v1/countries/atlantis"787980def test_country_topic(get):81 body = get("/countries/CAN/topics/economy").json()82 assert body["topic"]["id"] == "economy" and body["n_indicators"] > body["n_with_data"] > 083 subs = [s["subtopic"] for s in body["subtopics"]]84 assert subs[0] == "Output" # registry order85 all_inds = [i for s in body["subtopics"] for i in s["indicators"]]86 with_data = [i for i in all_inds if i["has_data"]]87 without = [i for i in all_inds if not i["has_data"]]88 assert with_data and without89 assert without[0]["provenance"] is None and without[0]["value"] is None90 for m in with_data:91 assert_provenance(m["provenance"])92 assert m["sparkline"]93 get("/countries/CAN/topics/astrology", status=404)949596def test_country_series(get):97 body = get("/countries/CAN/series/gdp?from=2010&to=2026&include_alt=true").json()98 assert_meta(body)99 assert body["indicator"]["id"] == "gdp" and body["country"]["id"] == "CAN" and body["unit"] == "current US$"100 years = [v["year"] for v in body["values"]]101 assert years == list(range(2010, 2027))102 fc = [v for v in body["values"] if v["is_forecast"]]103 assert [v["year"] for v in fc] == [2025, 2026] and fc[0]["source_id"] == "imf"104 assert body["values"][0]["value"] == pytest.approx(value("CAN", "gdp", 2010))105 for v in body["values"]:106 assert_provenance(v["provenance"])107 assert body["provenance"]["source"] == "worldbank"108 assert {s["source"] for s in body["sources"]} == {"worldbank", "imf"}109 assert body["alternatives"] and body["alternatives"][0]["source_id"] == "imf"110 assert body["stats"]["last"]["year"] == 2024 and body["stats"]["cagr"] == pytest.approx(4.03, abs=0.01)111 no_fc = get("/countries/CAN/series/gdp?include_forecast=false").json()112 assert all(not v["is_forecast"] for v in no_fc["values"])113 get("/countries/CAN/series/not-an-indicator", status=404)114 get("/countries/CAN/series/gdp?from=2020&to=2010", status=400)115116117def test_country_changes_events(get):118 ch = get("/countries/BRA/changes").json()119 assert ch["n"] > 0120 sev = [c["severity"] for c in ch["items"]]121 assert sev == sorted(sev, reverse=True)122 assert ch["items"][0]["headline"] and ch["items"][0]["indicator"]["slug"]123 assert_provenance(ch["items"][0]["provenance"])124 ev = get("/countries/USA/events?limit=5").json()125 assert 0 < ev["n"] <= 5 and ev["items"][0]["kind"] in ("sign_flip", "yoy_jump")126 get("/countries/ZZZ/changes", status=404)127128129def test_country_similar_insights_dna(get):130 sim = get("/countries/CAN/similar?mode=overall").json()131 assert sim["mode"] == "overall" and "economic" in sim["modes"]132 assert sim["peers"][0]["country"]["id"] in ("USA", "DEU", "FRA", "JPN") and sim["peers"][0]["score"] > 50133 assert sim["peers"][0]["contributions"]134 ins = get("/countries/CAN/insights").json()135 assert ins["items"] and "population grew" in ins["items"][0]["text"] or "Life expectancy" in ins["items"][0]["text"]136 assert any(i["provenance"] for i in ins["items"])137 dna = get("/countries/CAN/dna").json()138 assert dna["year_ref"] == 2024 and 0 <= dna["dims"]["income"] <= 100 and dna["dims"]["trade"] is None139 assert any(d["id"] == "income" and d["label"] == "Income" for d in dna["dimensions"])140141142def test_country_download(client):143 r = client.get("/api/v1/countries/CAN/download.csv")144 assert r.status_code == 200 and r.headers["content-type"].startswith("text/csv")145 assert 'filename="countryatlas-canada.csv"' in r.headers["content-disposition"]146 lines = r.text.splitlines()147 assert lines[0].startswith("# CountryAtlas export")148 rows = list(csv.DictReader(io.StringIO("\n".join(lines[1:]))))149 assert len(rows) > 400 and {"source", "series_code", "retrieved_at", "url", "licence"} <= set(rows[0])150 assert all(r["country_id"] == "CAN" for r in rows)151 j = client.get("/api/v1/countries/CAN/download.json?include_forecast=false").json()152 assert j["n"] == sum(1 for r in rows if r["is_forecast"] == "False") and j["rows"][0]["source"]153 assert client.get("/api/v1/countries/CAN/download.xml").status_code == 400154