spb/qwhpi Public
QHPI — Quebec Housing Price Index: quality-adjusted, hierarchically pooled housing price indexes.
Python 63.9%
TypeScript 25.4%
CSS 5.5%
TeX 3.5%
SQL 0.8%
Makefile 0.5%
Dockerfile 0.5%
1# =============================================================================2# QWHPI — Quebec Weekly Housing Price Index3# Author : Simon-Pierre Boucher4# Contact : contact@spboucher.ai5# File : api/tests/test_api.py6# Purpose : API tests (monthly v2.1) — every router, ETag / CSV / pagination.7# =============================================================================8"""API test suite (runs against the real canonical monthly parquet)."""910from __future__ import annotations1112import sys13from pathlib import Path1415sys.path.insert(0, str(Path(__file__).resolve().parents[1]))1617from fastapi.testclient import TestClient1819from app.main import app2021client = TestClient(app)222324def test_health():25 r = client.get("/health")26 assert r.status_code == 20027 assert r.json()["status"] == "ok"282930def test_meta_credits_author_and_monthly():31 r = client.get("/v1/meta")32 assert r.status_code == 20033 body = r.json()34 assert body["author"] == "Simon-Pierre Boucher"35 assert body["contact"] == "contact@spboucher.ai"36 assert body["frequency"] == "monthly"37 assert body["series_count"] > 10038 assert body["periods"] >= 60394041def test_index_series_full():42 r = client.get("/v1/index", params={"geography": "quebec-city", "type": "condo"})43 assert r.status_code == 20044 body = r.json()45 assert body["frequency"] == "monthly"46 obs = body["observations"]47 assert len(obs) == body["total_observations"] >= 6048 last = obs[-1]49 for key in ("lower_95", "upper_95", "reliability_grade", "transactions",50 "monthly_pct", "yoy_pct"):51 assert key in last # uncertainty is never hidden525354def test_index_pagination():55 r = client.get("/v1/index", params={"geography": "quebec", "type": "all",56 "limit": 10, "offset": 5})57 body = r.json()58 assert len(body["observations"]) == 1059 assert body["offset"] == 5606162def test_index_csv_export():63 r = client.get("/v1/index", params={"geography": "montreal", "type": "condo",64 "format": "csv"})65 assert r.status_code == 20066 assert r.headers["content-type"].startswith("text/csv")67 assert r.text.splitlines()[0].startswith("period")686970def test_index_etag_304():71 r1 = client.get("/v1/index/latest", params={"geography": "quebec", "type": "all"})72 etag = r1.headers.get("etag")73 assert etag74 r2 = client.get("/v1/index/latest", params={"geography": "quebec", "type": "all"},75 headers={"If-None-Match": etag})76 assert r2.status_code == 304777879def test_latest_excludes_partial_month():80 r = client.get("/v1/index/latest", params={"geography": "quebec", "type": "all"})81 body = r.json()82 assert body["is_partial_month"] is False83 assert body["reliability"] in "ABCDE"84 assert body["period"] >= "2026-06"858687def test_unknown_geography_404():88 r = client.get("/v1/index", params={"geography": "atlantis", "type": "all"})89 assert r.status_code == 404909192def test_bad_type_422():93 r = client.get("/v1/index", params={"geography": "quebec", "type": "castle"})94 assert r.status_code == 422959697def test_geographies_hierarchy():98 r = client.get("/v1/geographies")99 body = r.json()100 levels = {g["geography_level"] for g in body["published_series"]}101 assert levels == {"province", "region", "municipality"}102103104def test_coverage_matrix():105 r = client.get("/v1/geographies/coverage", params={"status": "published"})106 assert r.status_code == 200107 assert len(r.json()["coverage"]) > 0108109110def test_liquidity():111 r = client.get("/v1/liquidity", params={"geography": "montreal", "type": "condo"})112 body = r.json()113 assert body["median_monthly_transactions"] > 400114115116def test_compare_with_rebase():117 r = client.get("/v1/compare", params={118 "series": "montreal:condo,quebec-city:condo",119 "rebase_period": "2022-01"})120 body = r.json()121 assert set(body["series"]) == {"montreal:condo", "quebec-city:condo"}122 for obs in body["series"].values():123 first = [o for o in obs if o["period"] == "2022-01"][0]124 assert abs(first["index_smoothed"] - 100.0) < 1e-6125126127def test_map_region_yoy():128 r = client.get("/v1/map", params={"metric": "yoy", "level": "region"})129 body = r.json()130 assert len(body["cells"]) == 17131132133def test_vintages():134 r = client.get("/v1/vintages", params={"geography": "quebec", "type": "all"})135 assert r.status_code == 200136 assert len(r.json()["observations"]) >= 60137