feat: HUB-21 — réglages généraux du Hub consommés par ObsiGate (bannière + drapeau recherche_partout) #229
CI / lint (pull_request) Successful in 1m16s
CI / security (pull_request) Successful in 19s
CI / test (pull_request) Successful in 3m41s
CI / build (pull_request) Successful in 13s
CI / e2e (pull_request) Failing after 14m58s

This commit is contained in:
Spark
2026-11-06 17:02:04 +00:00
parent 15d015e59b
commit b88191ffcb
17 changed files with 647 additions and 15 deletions
+213
View File
@@ -0,0 +1,213 @@
# tests/test_hub_reglages.py
# Hub HUB-21 (propagation) — Réglages généraux du Hub consommés par
# ObsiGate : client (parsing de la forme figée, cache 60 s, repli
# intégral), bannière injectée côté serveur (page principale et page
# /recherche, texte échappé) et drapeau ``recherche_partout`` rendu
# en attribut de la page /recherche. Patron posé par l'hôte pilote
# TikTak 0.61.22. ``domaine_defaut`` n'est pas appliqué : ObsiGate
# n'a pas de notion de domaine propre (voir backend/hub_reglages.py).
import json
import time
import urllib.error
import pytest
from backend import hub_reglages
@pytest.fixture(autouse=True)
def _hub_env(monkeypatch):
"""Cache vierge + Hub de test explicite pour chaque test."""
hub_reglages.reinitialiser_cache()
monkeypatch.setenv("DRACO_HUB_URL", "https://hub-test.dracodev.net")
yield
hub_reglages.reinitialiser_cache()
def _reponse(corps=None, statut=200):
"""Fausse réponse urlopen portant un payload du contrat D6(a)."""
if corps is None:
corps = {"recherche_partout": True, "banniere": "",
"domaine_defaut": None, "ttl_secondes": 60}
charge = json.dumps(corps).encode("utf-8")
class _Resp:
status = statut
def read(self):
return charge
def __enter__(self):
return self
def __exit__(self, *exc):
return False
return _Resp()
def _patch_consultation(monkeypatch, reglages, appels=None):
"""Remplace la consultation HTTP par des réglages fixes."""
def _faux(url):
if appels is not None:
appels.append(url)
return reglages
monkeypatch.setattr(hub_reglages, "_consulter_hub", _faux)
# ── Client : parsing du contrat ─────────────────────────────────────
def test_consultation_parse_le_contrat(monkeypatch):
vu = {}
def _faux_urlopen(requete, timeout=None):
vu["url"] = requete.full_url
vu["cookie"] = requete.get_header("Cookie")
vu["accept"] = requete.get_header("Accept")
vu["timeout"] = timeout
return _reponse({"recherche_partout": False,
"banniere": "Maintenance dimanche",
"domaine_defaut": "famille", "ttl_secondes": 60})
monkeypatch.setattr(hub_reglages.urllib.request, "urlopen", _faux_urlopen)
reglages = hub_reglages.reglages_effectifs()
assert reglages == {"recherche_partout": False,
"banniere": "Maintenance dimanche",
"domaine_defaut": "famille"}
assert vu["url"] == "https://hub-test.dracodev.net/api/reglages/effectifs"
assert vu["cookie"] is None # endpoint public : aucun cookie
assert vu["accept"] == "application/json"
assert vu["timeout"] == 2 # délai du contrat
@pytest.mark.parametrize("corps", [
{"recherche_partout": "oui", "banniere": "", "domaine_defaut": None},
{"recherche_partout": True, "banniere": 42, "domaine_defaut": None},
{"recherche_partout": True, "banniere": "", "domaine_defaut": "travail"},
{"banniere": "", "domaine_defaut": None},
["pas", "un", "dict"],
])
def test_consultation_forme_inattendue_repli(monkeypatch, corps):
monkeypatch.setattr(hub_reglages.urllib.request, "urlopen",
lambda requete, timeout=None: _reponse(corps))
assert hub_reglages.reglages_effectifs() == hub_reglages.DEFAUTS
def test_consultation_erreur_http_repli(monkeypatch):
monkeypatch.setattr(hub_reglages.urllib.request, "urlopen",
lambda requete, timeout=None: _reponse(statut=500))
assert hub_reglages.reglages_effectifs() == hub_reglages.DEFAUTS
assert hub_reglages.banniere() == ""
assert hub_reglages.recherche_partout() is True
def test_consultation_hub_injoignable_repli(monkeypatch):
def _leve(requete, timeout=None):
raise urllib.error.URLError("injoignable")
monkeypatch.setattr(hub_reglages.urllib.request, "urlopen", _leve)
assert hub_reglages.reglages_effectifs() == hub_reglages.DEFAUTS
def test_consultation_json_illisible_repli(monkeypatch):
class _Resp:
status = 200
def read(self):
return b"pas du json"
def __enter__(self):
return self
def __exit__(self, *exc):
return False
monkeypatch.setattr(hub_reglages.urllib.request, "urlopen",
lambda requete, timeout=None: _Resp())
assert hub_reglages.reglages_effectifs() == hub_reglages.DEFAUTS
# ── Client : cache 60 s, ancien cache, pas de rafale ────────────────
def test_cache_60_s(monkeypatch):
appels = []
_patch_consultation(monkeypatch, {
"recherche_partout": False, "banniere": "Annonce A",
"domaine_defaut": None}, appels)
assert hub_reglages.banniere() == "Annonce A"
assert hub_reglages.recherche_partout() is False
assert len(appels) == 1 # cache 60 s
hub_reglages._cache_at = time.monotonic() - 61
assert hub_reglages.banniere() == "Annonce A"
assert len(appels) == 2 # expiration → rappel
def test_echec_ancien_cache_sans_rafale(monkeypatch):
appels = []
_patch_consultation(monkeypatch, {
"recherche_partout": True, "banniere": "Annonce A",
"domaine_defaut": None}, appels)
assert hub_reglages.banniere() == "Annonce A"
hub_reglages._cache_at = time.monotonic() - 61
_patch_consultation(monkeypatch, None, appels) # Hub tombe
assert hub_reglages.banniere() == "Annonce A" # ancien cache
assert len(appels) == 2 # pas de rafale
assert hub_reglages.banniere() == "Annonce A"
assert len(appels) == 2
# ── Intégration : bannière et drapeau dans les pages servies ────────
@pytest.fixture()
def hub_banniere(monkeypatch):
etat = {"reglages": None}
monkeypatch.setattr(hub_reglages, "_consulter_hub",
lambda url: etat["reglages"])
hub_reglages.reinitialiser_cache()
return etat
def test_banniere_presente_et_echappee_index(client, hub_banniere):
hub_banniere["reglages"] = {
"recherche_partout": True,
"banniere": "Maintenance dimanche <b>gras</b> & cie",
"domaine_defaut": None}
r = client.get("/")
assert r.status_code == 200
assert 'id="banniere-hub"' in r.text
assert "Maintenance dimanche &lt;b&gt;gras&lt;/b&gt; &amp; cie" in r.text
assert "obsigate_banniere_masquee" in r.text
def test_banniere_absente_si_vide_ou_hub_muet(client, hub_banniere):
hub_banniere["reglages"] = {
"recherche_partout": True, "banniere": "", "domaine_defaut": "pro"}
r = client.get("/")
assert r.status_code == 200
assert "banniere-hub" not in r.text
hub_banniere["reglages"] = None
hub_reglages.reinitialiser_cache()
r = client.get("/")
assert r.status_code == 200
assert "banniere-hub" not in r.text
def test_recherche_banniere_et_drapeau(client, hub_banniere):
hub_banniere["reglages"] = {
"recherche_partout": False, "banniere": "Annonce recherche",
"domaine_defaut": None}
r = client.get("/recherche")
assert r.status_code == 200
assert 'data-recherche-partout="false"' in r.text
assert 'id="banniere-hub"' in r.text
assert "Annonce recherche" in r.text
hub_banniere["reglages"] = {
"recherche_partout": True, "banniere": "", "domaine_defaut": None}
hub_reglages.reinitialiser_cache()
r = client.get("/recherche")
assert 'data-recherche-partout="true"' in r.text
assert "banniere-hub" not in r.text
hub_banniere["reglages"] = None # Hub muet → défaut actif
hub_reglages.reinitialiser_cache()
r = client.get("/recherche")
assert 'data-recherche-partout="true"' in r.text