SPB Git forge

spb/api-ka

Public

API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com

48commits 1branches 0releases
5.9 MBsize
maindefault branch
20 days agolast push
Python 60.9% HTML 21% TypeScript 7.3% JavaScript 5.2% CSS 4.8% Shell 0.8%
5.6 KB · 160 lines python
Raw Blame History
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