france-fire-3d-live / tests /test_api.py
Mike0021's picture
Initial live 3D wildfire map: FIRMS proxy + MapLibre 3D frontend
f62edf8 verified
Raw
History Blame Contribute Delete
4.91 kB
"""Tests des endpoints : succès, vide, erreur amont, timeout, cache périmé,
rate-limit du refresh, santé."""
import time
import pytest
from fastapi.testclient import TestClient
import app as app_module
import sources
FIRE_OK = {
"id": "abc123", "lat": 43.3, "lon": 5.4,
"acquired_at": "2026-07-27T01:37:00Z",
"source": "NASA FIRMS VIIRS S-NPP (Europe 24 h)", "satellite": "N",
"instrument": "VIIRS", "frp": 12.3, "brightness": 312.5,
"confidence": "nominal", "daynight": "N", "type": "thermal_anomaly",
}
def _result(fires, used=None):
sts = [sources.SourceStatus(name=u or "src", ok=True, latency_ms=42,
last_success="2026-07-27T10:00:00Z")
for u in (used or ["src"])]
return sources.FetchResult(fires=fires, sources_used=used or ["src"], statuses=sts)
@pytest.fixture()
def client(monkeypatch):
monkeypatch.setattr(app_module, "_cache", {"payload": None, "fetched_at": 0.0})
monkeypatch.setattr(app_module, "_last_forced", 0.0)
monkeypatch.setattr(app_module, "_last_statuses", [])
return TestClient(app_module.app)
def test_fires_succes(client, monkeypatch):
monkeypatch.setattr(sources, "fetch_all", lambda: _result([FIRE_OK]))
r = client.get("/api/fires")
assert r.status_code == 200
body = r.json()
assert body["status"] == "ok"
assert body["count"] == 1
assert body["fires"][0]["id"] == "abc123"
assert body["data_observed_at"] == "2026-07-27T01:37:00Z"
assert body["stale"] is False
assert body["errors"] == []
assert body["attribution"]
def test_fires_vide_honnete(client, monkeypatch):
"""Amont OK mais zéro feu : état vide honnête, pas de données fabriquées."""
monkeypatch.setattr(sources, "fetch_all", lambda: _result([]))
body = client.get("/api/fires").json()
assert body["status"] == "ok"
assert body["count"] == 0
assert body["fires"] == []
assert body["data_observed_at"] is None
def test_erreur_amont_sans_cache(client, monkeypatch):
def boom():
raise sources.UpstreamError("toutes les sources ont échoué")
monkeypatch.setattr(sources, "fetch_all", boom)
body = client.get("/api/fires").json()
assert body["status"] == "error"
assert body["fires"] == []
assert body["errors"]
def test_cache_frais_pas_de_nouvel_appel(client, monkeypatch):
appels = []
def fake():
appels.append(1)
return _result([FIRE_OK])
monkeypatch.setattr(sources, "fetch_all", fake)
client.get("/api/fires")
client.get("/api/fires")
assert len(appels) == 1 # 2e appel servi par le cache
def test_cache_perime_apres_echec(client, monkeypatch):
monkeypatch.setattr(sources, "fetch_all", lambda: _result([FIRE_OK]))
first = client.get("/api/fires").json()
assert first["stale"] is False
# Cache expiré + amont en panne -> repli sur cache marqué périmé
app_module._cache["fetched_at"] = time.time() - (app_module.TTL_S + 10)
def boom():
raise sources.UpstreamError("timeout amont")
monkeypatch.setattr(sources, "fetch_all", boom)
body = client.get("/api/fires").json()
assert body["status"] == "ok"
assert body["stale"] is True
assert body["count"] == 1
assert body["errors"]
def test_timeout_traite_comme_erreur(client, monkeypatch):
def boom():
raise sources.UpstreamError("ReadTimeout: délai dépassé")
monkeypatch.setattr(sources, "fetch_all", boom)
body = client.get("/api/fires").json()
assert body["status"] == "error"
def test_refresh_force_et_rate_limit(client, monkeypatch):
appels = []
def fake():
appels.append(1)
return _result([FIRE_OK])
monkeypatch.setattr(sources, "fetch_all", fake)
client.get("/api/fires")
app_module._cache["fetched_at"] = time.time() - (app_module.TTL_S + 10)
r = client.post("/api/refresh")
assert r.status_code == 200
assert len(appels) == 2
# Second refresh dans les 30 s : pas de nouvel appel amont
app_module._cache["fetched_at"] = time.time() - (app_module.TTL_S + 10)
client.post("/api/refresh")
assert len(appels) == 2
def test_health_apres_succes(client, monkeypatch):
monkeypatch.setattr(sources, "fetch_all", lambda: _result([FIRE_OK], ["NASA FIRMS test"]))
client.get("/api/fires")
r = client.get("/api/health")
assert r.status_code == 200
body = r.json()
assert body["status"] in ("ok", "degraded")
assert body["version"]
assert body["time"].endswith("Z")
assert body["sources"][0]["name"] == "NASA FIRMS test"
assert body["sources"][0]["ok"] is True
assert body["cache"]["ttl_s"] == app_module.TTL_S
assert body["cache"]["age_s"] is not None
def test_health_sans_donnees(client):
body = client.get("/api/health").json()
assert body["status"] == "degraded"
assert body["cache"]["age_s"] is None