API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com
Python 60.9%
HTML 21%
TypeScript 7.3%
JavaScript 5.2%
CSS 4.8%
Shell 0.8%
1# ============================================2# Projet : API-KA3# Fichier : tests/test_stats.py4# Node : m3u96b5# Author : Simon-Pierre Boucher6# Contact : contact@spboucher.ai7# Date : 2026-08-178# ============================================9"""Tests des routes /api/stats : dashboard (contrat SPEC), rapport PDF, page /stats."""1011from __future__ import annotations1213import datetime1415import pytest16from fastapi.testclient import TestClient1718from src.api import reqstats19from src.api.main import app20from src.api.routes import stats as stats_module21from src.database.db import session_scope22from src.database.models import ApiRequest, CollectionRun232425@pytest.fixture(autouse=True)26def _clear_stats_cache():27 """Le cache 5 min du dashboard ne doit pas fuiter entre les tests."""28 stats_module._cache.clear()29 yield30 stats_module._cache.clear()313233def _seed_requests_and_run() -> None:34 now = datetime.datetime.now(tz=datetime.UTC)35 today = datetime.datetime.now(tz=stats_module.TZ).date()36 with session_scope() as session:37 if session.query(ApiRequest).count() == 0:38 for i in range(5):39 session.add(40 ApiRequest(41 ts=now - datetime.timedelta(hours=i + 1),42 method="GET",43 endpoint="/api/v1/louka",44 status=200 if i else 404,45 duration_ms=10.0 + i,46 )47 )48 session.add(49 CollectionRun(50 service="louka",51 date_key=today,52 status="success",53 records_count=42,54 duration_seconds=2.0,55 error_message=None,56 node="m3u96b",57 started_at=now,58 finished_at=now,59 )60 )616263def test_dashboard_contract() -> None:64 _seed_requests_and_run()65 with TestClient(app) as client:66 response = client.get("/api/stats/dashboard?period=7j")67 assert response.status_code == 20068 body = response.json()69 assert body["success"] is True70 dash = body["data"]71 assert set(dash) >= {"updated", "period", "kpis", "series", "tables", "records"}72 kpis = {k["id"]: k for k in dash["kpis"]}73 assert kpis["calls"]["value"] >= 574 assert kpis["records"]["value"] >= 4275 assert any(s["id"] == "calls" for s in dash["series"])767778def test_dashboard_invalid_period() -> None:79 with TestClient(app) as client:80 assert client.get("/api/stats/dashboard?period=xx").status_code == 422818283def test_report_pdf() -> None:84 _seed_requests_and_run()85 with TestClient(app) as client:86 response = client.get("/api/stats/report?period=7j&mode=synthese")87 assert response.status_code == 20088 assert response.headers["content-type"] == "application/pdf"89 assert "groupe-ka_api-ka_stats_7j_" in response.headers["content-disposition"]90 assert response.content.startswith(b"%PDF")919293def test_stats_page_and_middleware_recording() -> None:94 with TestClient(app) as client:95 response = client.get("/stats")96 assert response.status_code == 20097 assert "tableau de bord" in response.text.lower() or "stats" in response.text.lower()98 # Le middleware a empilé la requête ; flush() la persiste en base.99 reqstats.flush()100 with session_scope() as session:101 assert (102 session.query(ApiRequest).filter(ApiRequest.endpoint == "/stats").count()103 >= 1104 )105106107def test_ecosystem_pdf_builder() -> None:108 """EcosystemReport : PDF consolidé hors-ligne, avec une plateforme109 disponible et une injoignable (section « données indisponibles »)."""110 from src.api import ecopdf111112 platforms = [113 {114 "site": {115 "id": "lou-ka",116 "wordmark": "Lou·Ka",117 "accent": "#ff6a00",118 "domain": "www.lou-ka.com",119 "tagline": "Tous les logements à louer",120 },121 "dashboard": {122 "updated": "2026-08-17T12:00:00-04:00",123 "period": {"from": "2026-08-11", "to": "2026-08-17", "label": "7 jours"},124 "kpis": [125 {"id": "actives", "label": "Annonces actives", "value": 33907},126 {"id": "nouvelles", "label": "Nouvelles annonces (période)", "value": 29428},127 ],128 "records": [{"label": "Jour record", "value": "412 annonces", "date": "2026-08-09"}],129 },130 "error": None,131 },132 {133 "site": {134 "id": "immo-ka",135 "wordmark": "Immo·Ka",136 "accent": "#e23744",137 "domain": "www.immo-ka.com",138 "tagline": "Toutes les propriétés à vendre",139 },140 "dashboard": None,141 "error": "délai dépassé (15 s)",142 },143 ]144 for mode in ("complet", "synthese"):145 pdf = ecopdf.EcosystemReport(146 platforms=platforms, period={"label": "7 jours"}, mode=mode147 ).build()148 assert pdf.startswith(b"%PDF")149 assert ecopdf.main_kpi(platforms[0]["dashboard"])["id"] == "actives"150 assert ecopdf.new_kpi(platforms[0]["dashboard"])["id"] == "nouvelles"151152153def test_normalize_endpoint() -> None:154 assert reqstats.normalize_endpoint("/api/v1/louka/date/2026-08-16") == (155 "/api/v1/louka/date/{date}"156 )157 assert reqstats.normalize_endpoint("/api/v1/louka/latest") == "/api/v1/louka/latest"158 assert reqstats.normalize_endpoint("/ka/tokens.css") == "/ka/*"159 assert reqstats.normalize_endpoint("/wp-admin/setup.php") == "(autre)"160