SPB Git

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

5.2 KB · 161 lines python
1# ============================================2# Projet   : API-KA3# Fichier  : tests/test_collectors.py4# Node     : m3u96b5# Author   : Simon-Pierre Boucher6# Contact  : contact@spboucher.ai7# Date     : 2026-08-168# ============================================9"""Tests des collecteurs : pipeline complet, déduplication, échecs et statut retried."""1011from __future__ import annotations1213import datetime1415import pytest16from sqlalchemy import func, select1718from src.collectors import COLLECTORS, build_collectors, get_collector19from src.collectors.louka_collector import LoukaCollector20from src.config import SERVICES21from src.database.db import session_scope22from src.database.models import DATA_MODELS, CollectionRun2324SAMPLE_PAYLOAD = [25    {"id": 1, "nom": "enregistrement-a"},26    {"id": 2, "nom": "enregistrement-b"},27]282930def _count_rows(service: str, date_key: datetime.date) -> int:31    model = DATA_MODELS[service]32    with session_scope() as session:33        return (34            session.execute(35                select(func.count())36                .select_from(model)37                .where(model.source == service, model.date_key == date_key)38            ).scalar()39            or 040        )414243def _last_run(service: str, date_key: datetime.date) -> CollectionRun | None:44    with session_scope() as session:45        return (46            session.execute(47                select(CollectionRun)48                .where(49                    CollectionRun.service == service, CollectionRun.date_key == date_key50                )51                .order_by(CollectionRun.id.desc())52                .limit(1)53            )54            .scalars()55            .first()56        )575859def test_registry_covers_all_services() -> None:60    """Chaque service KA a un collecteur enregistré."""61    assert set(COLLECTORS) == set(SERVICES)62    assert len(build_collectors()) == 863    with pytest.raises(ValueError):64        get_collector("inconnu")656667def test_run_success(monkeypatch: pytest.MonkeyPatch) -> None:68    """Le pipeline complet insère les données et journalise un run success."""69    date_key = datetime.date(2026, 8, 1)70    collector = LoukaCollector(attempts=1, delays=(0,))71    monkeypatch.setattr(collector, "fetch", lambda: SAMPLE_PAYLOAD)7273    result = collector.run(date_key=date_key)7475    assert result["status"] == "success"76    assert result["records_count"] == 277    assert _count_rows("louka", date_key) == 278    run = _last_run("louka", date_key)79    assert run is not None80    assert run.status == "success"81    assert run.records_count == 282    assert run.node838485def test_run_deduplicates_by_checksum(monkeypatch: pytest.MonkeyPatch) -> None:86    """Une seconde collecte du même payload n'insère aucun doublon."""87    date_key = datetime.date(2026, 8, 2)88    collector = LoukaCollector(attempts=1, delays=(0,))89    monkeypatch.setattr(collector, "fetch", lambda: SAMPLE_PAYLOAD)9091    first = collector.run(date_key=date_key)92    second = collector.run(date_key=date_key)9394    assert first["records_count"] == 295    assert second["status"] == "success"96    assert second["records_count"] == 097    assert _count_rows("louka", date_key) == 29899100def test_run_failure_after_retries(monkeypatch: pytest.MonkeyPatch) -> None:101    """Après épuisement des relances : run failed + message d'erreur journalisé."""102    date_key = datetime.date(2026, 8, 3)103    collector = LoukaCollector(attempts=2, delays=(0, 0))104105    def broken() -> None:106        raise RuntimeError("source indisponible")107108    monkeypatch.setattr(collector, "fetch", broken)109110    result = collector.run(date_key=date_key)111112    assert result["status"] == "failed"113    assert "source indisponible" in (result["error"] or "")114    run = _last_run("louka", date_key)115    assert run is not None116    assert run.status == "failed"117    assert run.records_count == 0118    assert "source indisponible" in (run.error_message or "")119120121def test_run_retried_status(monkeypatch: pytest.MonkeyPatch) -> None:122    """Un succès après relance est journalisé avec le statut retried."""123    date_key = datetime.date(2026, 8, 4)124    collector = LoukaCollector(attempts=2, delays=(0, 0))125    calls = {"count": 0}126127    def flaky() -> list[dict]:128        calls["count"] += 1129        if calls["count"] == 1:130            raise RuntimeError("transitoire")131        return SAMPLE_PAYLOAD132133    monkeypatch.setattr(collector, "fetch", flaky)134135    result = collector.run(date_key=date_key)136137    assert result["status"] == "retried"138    assert result["records_count"] == 2139    run = _last_run("louka", date_key)140    assert run is not None141    assert run.status == "retried"142143144def test_validate_rejects_empty_payload() -> None:145    """Un payload vide ou None est rejeté par la validation."""146    collector = LoukaCollector(attempts=1, delays=(0,))147    with pytest.raises(ValueError):148        collector.validate(None)149    with pytest.raises(ValueError):150        collector.validate([])151152153def test_checksum_is_stable_and_order_insensitive() -> None:154    """Le checksum SHA-256 est canonique (indépendant de l'ordre des clés)."""155    a = LoukaCollector.checksum({"x": 1, "y": 2})156    b = LoukaCollector.checksum({"y": 2, "x": 1})157    c = LoukaCollector.checksum({"x": 1, "y": 3})158    assert a == b159    assert a != c160    assert len(a) == 64161
Files · c047f3cdc8769817c51c4663b16200363c6b7876
Menu

Theme