spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
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 from countryatlas.registry import topics as registry_topics4546 headline = {m["indicator"]: m for m in body["headline"]}47 assert list(headline) == registry_topics()["headline"] # read from topics.yaml at request time (cache cleared per snapshot)48 assert list(headline)[:3] == ["population", "gdp", "gdp-per-capita"]49 # indicators in the headline but absent from the snapshot still render as "no data"50 absent = [m for m in body["headline"] if m["indicator"] not in ("population", "gdp", "gdp-per-capita", "gdp-growth", "inflation",51 "unemployment-rate", "life-expectancy", "median-age", "co2-per-capita", "renewable-electricity-share", "internet-users",52 "government-debt-pct-gdp")]53 assert all(m["has_data"] is False and m["formatted"] == "—" and m["provenance"] is None for m in absent)54 gpc = headline["gdp-per-capita"]55 assert gpc["has_data"] and gpc["year"] == 2024 and gpc["value"] == pytest.approx(value("CAN", "gdp-per-capita", 2024))56 assert gpc["change"]["pct"] == pytest.approx(3.0) and gpc["change"]["formatted"] == "+3.0 %"57 assert gpc["rank_world"] and gpc["n_world"] == 8 and gpc["rank_region"] and gpc["n_region"] == 258 assert gpc["formatted"].startswith("US$") and gpc["formatted"].endswith("k") and gpc["formatted_short"] == gpc["formatted"][3:]59 assert headline["inflation"]["formatted"].endswith(" %") and headline["life-expectancy"]["formatted"].endswith(" yrs")60 assert len(gpc["sparkline"]) == 30 and gpc["sparkline"][-1][0] == 2024 and isinstance(gpc["sparkline"][-1][0], int)61 assert_provenance(gpc["provenance"])62 assert gpc["provenance"]["url"] == "https://data.worldbank.org/indicator/NY.GDP.PCAP.CD?locations=CA"63 assert gpc["provenance"]["series_code"] == "NY.GDP.PCAP.CD" and gpc["provenance"]["licence"] == "CC BY 4.0"64 # OWID provenance for co265 assert headline["co2-per-capita"]["provenance"]["url"] == "https://github.com/owid/co2-data"66 assert headline["median-age"]["provenance"]["url"] == "https://ourworldindata.org/grapher/median-age"67 topics = {t["id"]: t for t in body["topics"]}68 assert topics["economy"]["n_with_data"] >= 5 and topics["income"]["n_with_data"] == 069 assert [t["order"] for t in body["topics"]] == sorted(t["order"] for t in body["topics"])70 assert body["neighbours"][0]["id"] == "USA"717273def test_country_case_insensitive_and_iso2(get):74 assert get("/countries/can").json()["country"]["id"] == "CAN"75 assert get("/countries/CANADA").json()["country"]["id"] == "CAN"76 assert get("/countries/ca").json()["country"]["id"] == "CAN"777879def test_country_404_problem_json(client):80 r = client.get("/api/v1/countries/atlantis")81 assert r.status_code == 40482 assert r.headers["content-type"].startswith("application/problem+json")83 body = r.json()84 assert body["title"] == "Country not found" and body["status"] == 404 and "atlantis" in body["detail"]85 assert body["instance"] == "/api/v1/countries/atlantis"868788def test_country_topic(get):89 body = get("/countries/CAN/topics/economy").json()90 assert body["topic"]["id"] == "economy" and body["n_indicators"] > body["n_with_data"] > 091 subs = [s["subtopic"] for s in body["subtopics"]]92 assert subs[0] == "Output" # registry order93 all_inds = [i for s in body["subtopics"] for i in s["indicators"]]94 with_data = [i for i in all_inds if i["has_data"]]95 without = [i for i in all_inds if not i["has_data"]]96 assert with_data and without97 assert without[0]["provenance"] is None and without[0]["value"] is None98 for m in with_data:99 assert_provenance(m["provenance"])100 assert m["sparkline"]101 get("/countries/CAN/topics/astrology", status=404)102103104def test_country_series(get):105 body = get("/countries/CAN/series/gdp?from=2010&to=2026&include_alt=true").json()106 assert_meta(body)107 assert body["indicator"]["id"] == "gdp" and body["country"]["id"] == "CAN" and body["unit"] == "current US$"108 years = [v["year"] for v in body["values"]]109 assert years == list(range(2010, 2027))110 fc = [v for v in body["values"] if v["is_forecast"]]111 assert [v["year"] for v in fc] == [2025, 2026] and fc[0]["source_id"] == "imf"112 assert body["values"][0]["value"] == pytest.approx(value("CAN", "gdp", 2010))113 for v in body["values"]:114 assert_provenance(v["provenance"])115 assert body["provenance"]["source"] == "worldbank"116 assert {s["source"] for s in body["sources"]} == {"worldbank", "imf"}117 assert body["alternatives"] and body["alternatives"][0]["source_id"] == "imf"118 assert body["stats"]["last"]["year"] == 2024 and body["stats"]["cagr"] == pytest.approx(4.03, abs=0.01)119 no_fc = get("/countries/CAN/series/gdp?include_forecast=false").json()120 assert all(not v["is_forecast"] for v in no_fc["values"])121 get("/countries/CAN/series/not-an-indicator", status=404)122 get("/countries/CAN/series/gdp?from=2020&to=2010", status=400)123124125def test_country_changes_events(get):126 ch = get("/countries/BRA/changes").json()127 assert ch["n"] > 0128 sev = [c["severity"] for c in ch["items"]]129 assert sev == sorted(sev, reverse=True)130 assert ch["items"][0]["headline"] and ch["items"][0]["indicator"]["slug"]131 assert_provenance(ch["items"][0]["provenance"])132 ev = get("/countries/USA/events?limit=5").json()133 assert 0 < ev["n"] <= 5 and ev["items"][0]["kind"] in ("sign_flip", "yoy_jump")134 get("/countries/ZZZ/changes", status=404)135136137def test_country_similar_insights_dna(get):138 sim = get("/countries/CAN/similar?mode=overall").json()139 assert sim["mode"] == "overall" and "economic" in sim["modes"]140 assert sim["peers"][0]["country"]["id"] in ("USA", "DEU", "FRA", "JPN") and sim["peers"][0]["score"] > 50141 assert sim["peers"][0]["contributions"]142 ins = get("/countries/CAN/insights").json()143 assert ins["items"] and "population grew" in ins["items"][0]["text"] or "Life expectancy" in ins["items"][0]["text"]144 assert any(i["provenance"] for i in ins["items"])145 dna = get("/countries/CAN/dna").json()146 assert dna["year_ref"] == 2024 and 0 <= dna["dims"]["income"] <= 100 and dna["dims"]["trade"] is None147 assert any(d["id"] == "income" and d["label"] == "Income" for d in dna["dimensions"])148149150def test_country_download(client):151 r = client.get("/api/v1/countries/CAN/download.csv")152 assert r.status_code == 200 and r.headers["content-type"].startswith("text/csv")153 assert 'filename="countryatlas-canada.csv"' in r.headers["content-disposition"]154 lines = r.text.splitlines()155 assert lines[0].startswith("# CountryAtlas export")156 rows = list(csv.DictReader(io.StringIO("\n".join(lines[1:]))))157 assert len(rows) > 400 and {"source", "series_code", "retrieved_at", "url", "licence"} <= set(rows[0])158 assert all(r["country_id"] == "CAN" for r in rows)159 j = client.get("/api/v1/countries/CAN/download.json?include_forecast=false").json()160 assert j["n"] == sum(1 for r in rows if r["is_forecast"] == "False") and j["rows"][0]["source"]161 assert client.get("/api/v1/countries/CAN/download.xml").status_code == 400162