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_api.py4# Node : m3u96b5# Author : Simon-Pierre Boucher6# Contact : contact@spboucher.ai7# Date : 2026-08-168# ============================================9"""Tests des routes de l'API publique : format uniforme, pagination, 404, /health."""1011from __future__ import annotations1213import datetime14import hashlib15import json1617import pytest18from fastapi.testclient import TestClient1920from src.api.main import app21from src.database.db import session_scope22from src.database.models import DATA_MODELS, CollectionRun2324SEED_DATE = datetime.date(2026, 8, 10)252627def _seed_louka() -> None:28 model = DATA_MODELS["louka"]29 now = datetime.datetime.now(tz=datetime.UTC)30 with session_scope() as session:31 for i in range(3):32 payload = {"id": i, "nom": f"louka-{i}"}33 checksum = hashlib.sha256(34 json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()35 ).hexdigest()36 exists = (37 session.query(model)38 .filter_by(source="louka", date_key=SEED_DATE, checksum=checksum)39 .first()40 )41 if not exists:42 session.add(43 model(44 payload=payload,45 source="louka",46 collected_at=now,47 date_key=SEED_DATE,48 checksum=checksum,49 )50 )51 session.add(52 CollectionRun(53 service="louka",54 date_key=SEED_DATE,55 status="success",56 records_count=3,57 duration_seconds=1.2,58 error_message=None,59 node="m3u96b",60 started_at=now,61 finished_at=now,62 )63 )646566@pytest.fixture(scope="module")67def client():68 """Client de test FastAPI avec cycle de vie (lifespan) actif."""69 _seed_louka()70 with TestClient(app) as test_client:71 yield test_client727374def test_root(client: TestClient) -> None:75 """GET / retourne le statut de la plateforme et la version."""76 response = client.get("/")77 assert response.status_code == 20078 body = response.json()79 assert body["success"] is True80 assert body["data"]["platform"] == "API-KA"81 assert body["data"]["version"]82 assert "node" in body["meta"]838485def test_root_serves_web_platform_to_browsers(client: TestClient) -> None:86 """GET / avec Accept: text/html sert la plateforme web (docs + playground)."""87 response = client.get("/", headers={"Accept": "text/html"})88 assert response.status_code == 20089 assert response.headers["content-type"].startswith("text/html")90 assert "Playground" in response.text91 assert "API-KA" in response.text929394def test_health(client: TestClient) -> None:95 """GET /health expose le node, l'état DB et les dernières collectes."""96 response = client.get("/health")97 assert response.status_code == 20098 data = response.json()["data"]99 assert data["database"] == "ok"100 assert data["required_node"] == "m3u96b"101 assert data["last_collections"]["louka"] is not None102 assert data["last_collections"]["louka"]["records_count"] == 3103104105def test_list_service_data(client: TestClient) -> None:106 """GET /api/v1/louka pagine les données au format uniforme."""107 response = client.get("/api/v1/louka", params={"page": 1, "limit": 2})108 assert response.status_code == 200109 body = response.json()110 assert body["success"] is True111 assert len(body["data"]) == 2112 assert body["meta"]["page"] == 1113 assert body["meta"]["limit"] == 2114 assert body["meta"]["total"] >= 3115116117def test_latest(client: TestClient) -> None:118 """GET /api/v1/louka/latest retourne la collecte la plus récente."""119 response = client.get("/api/v1/louka/latest")120 assert response.status_code == 200121 body = response.json()122 assert body["meta"]["date_key"] == SEED_DATE.isoformat()123 assert len(body["data"]) == 3124125126def test_by_date(client: TestClient) -> None:127 """GET /api/v1/louka/date/{date} filtre par date logique."""128 response = client.get(f"/api/v1/louka/date/{SEED_DATE.isoformat()}")129 assert response.status_code == 200130 assert response.json()["meta"]["total"] == 3131132 empty = client.get("/api/v1/louka/date/2020-01-01")133 assert empty.status_code == 200134 assert empty.json()["meta"]["total"] == 0135136 invalid = client.get("/api/v1/louka/date/pas-une-date")137 assert invalid.status_code == 422138139140def test_stats(client: TestClient) -> None:141 """GET /api/v1/louka/stats agrège par jour et expose la dernière réussite."""142 response = client.get("/api/v1/louka/stats")143 assert response.status_code == 200144 data = response.json()["data"]145 assert data["total_records"] >= 3146 assert any(day["date_key"] == SEED_DATE.isoformat() for day in data["days"])147 assert data["last_success"]["status"] == "success"148149150def test_unknown_service_is_404(client: TestClient) -> None:151 """Tout service hors de la liste officielle → 404."""152 assert client.get("/api/v1/inconnu").status_code == 404153 assert client.get("/api/v1/inconnu/latest").status_code == 404154 assert client.get("/api/v1/inconnu/stats").status_code == 404155156157def test_limit_is_capped_at_500(client: TestClient) -> None:158 """limit > 500 est rejeté (422)."""159 response = client.get("/api/v1/louka", params={"limit": 501})160 assert response.status_code == 422161162163def test_runs_endpoint(client: TestClient) -> None:164 """GET /api/v1/runs liste l'historique, filtrable par service et statut."""165 response = client.get("/api/v1/runs", params={"service": "louka"})166 assert response.status_code == 200167 body = response.json()168 assert body["meta"]["total"] >= 1169 assert all(run["service"] == "louka" for run in body["data"])170171 assert client.get("/api/v1/runs", params={"service": "inconnu"}).status_code == 404172 assert client.get("/api/v1/runs", params={"status": "invalide"}).status_code == 422173