"""HUB-21 — réglages généraux du Hub consommés par Kanbante. Propagation du pilote TikTak 0.61.22 : client serveur ``app.hub_reglages`` (cache 60 s, horodatage posé avant l'appel, repli intégral sur les défauts actuels), bannière d'annonce dans le partiel ``_header.html`` (texte échappé par l'autoescape Jinja) et drapeau ``recherche_partout`` rendu en attribut ``data-recherche-partout`` de la page /recherche (la logique JS associée est couverte par tests/recherche.test.js). ``domaine_defaut`` n'est pas appliqué : Kanbante n'a pas de notion de domaine propre (voir le docstring du module). """ import httpx import pytest from app import hub_reglages class _Reponse: def __init__(self, status_code=200, donnees=None): self.status_code = status_code self._donnees = donnees def json(self): if isinstance(self._donnees, Exception): raise self._donnees return self._donnees @pytest.fixture(autouse=True) def _cache_propre(): hub_reglages._reset_cache() yield hub_reglages._reset_cache() @pytest.fixture() def faux_hub(monkeypatch): """Faux Hub des réglages : état pilotable + journal d'appels.""" etat = {"reglages": None, "appels": [], "mode": "ok"} def faux_get(url, headers=None, timeout=None): etat["appels"].append( {"url": url, "headers": dict(headers or {}), "timeout": timeout}) if etat["mode"] == "panne": raise httpx.ConnectError("Hub injoignable (simulation)") if etat["mode"] == "erreur500": return _Reponse(500, {"detail": "erreur"}) return _Reponse(200, etat["reglages"]) monkeypatch.setattr(httpx, "get", faux_get) return etat # ── Client : validation, contrat d'appel, résolution d'URL ────────── def test_validation_forme_du_contrat(): assert hub_reglages._valider({ "recherche_partout": False, "banniere": "Maintenance dimanche", "domaine_defaut": "famille", "ttl_secondes": 60}) == { "recherche_partout": False, "banniere": "Maintenance dimanche", "domaine_defaut": "famille"} for charge in ( None, "texte", ["x"], {}, {"recherche_partout": "oui", "banniere": "", "domaine_defaut": None}, {"recherche_partout": True, "banniere": 42, "domaine_defaut": None}, {"recherche_partout": True, "banniere": "", "domaine_defaut": "travail"}, ): assert hub_reglages._valider(charge) is None, charge def test_appel_contrat_exact_et_cache(faux_hub): faux_hub["reglages"] = { "recherche_partout": False, "banniere": "Annonce A", "domaine_defaut": None, "ttl_secondes": 60} assert hub_reglages.banniere() == "Annonce A" assert hub_reglages.recherche_partout() is False assert len(faux_hub["appels"]) == 1 # cache 60 s appel = faux_hub["appels"][0] assert appel["url"] == "https://hub.dracodev.net/api/reglages/effectifs" assert appel["headers"]["Accept"] == "application/json" assert "Cookie" not in appel["headers"] # endpoint public assert appel["timeout"] == 2.0 def test_url_hub_par_hote_dev(faux_hub): faux_hub["reglages"] = { "recherche_partout": True, "banniere": "", "domaine_defaut": None} hub_reglages.reglages_effectifs(hote="kanbante-dev.dracodev.net") assert faux_hub["appels"][0]["url"] == ( "https://hub-dev.dracodev.net/api/reglages/effectifs") def test_ttl_expire_reconsulte(faux_hub): faux_hub["reglages"] = { "recherche_partout": True, "banniere": "Annonce A", "domaine_defaut": None} assert hub_reglages.banniere() == "Annonce A" hub_reglages._cache_fetched_at -= hub_reglages.CACHE_TTL + 1 faux_hub["reglages"] = { "recherche_partout": True, "banniere": "Annonce B", "domaine_defaut": None} assert hub_reglages.banniere() == "Annonce B" assert len(faux_hub["appels"]) == 2 def test_hub_muet_repli_defauts(faux_hub): faux_hub["mode"] = "panne" assert hub_reglages.reglages_effectifs() == hub_reglages.DEFAUTS hub_reglages._reset_cache() faux_hub["mode"] = "erreur500" assert hub_reglages.reglages_effectifs() == hub_reglages.DEFAUTS hub_reglages._reset_cache() faux_hub["mode"] = "ok" faux_hub["reglages"] = {"forme": "inattendue"} assert hub_reglages.reglages_effectifs() == hub_reglages.DEFAUTS assert hub_reglages.banniere() == "" assert hub_reglages.recherche_partout() is True def test_echec_ancien_cache_sans_rafale(faux_hub): faux_hub["reglages"] = { "recherche_partout": True, "banniere": "Annonce A", "domaine_defaut": None} assert hub_reglages.banniere() == "Annonce A" hub_reglages._cache_fetched_at -= hub_reglages.CACHE_TTL + 1 faux_hub["mode"] = "panne" # Hub tombe assert hub_reglages.banniere() == "Annonce A" # ancien cache appels = len(faux_hub["appels"]) assert hub_reglages.banniere() == "Annonce A" assert len(faux_hub["appels"]) == appels # pas de rafale # ── Intégration : bannière et drapeau dans les pages ──────────────── def test_banniere_presente_et_echappee(client, faux_hub): faux_hub["reglages"] = { "recherche_partout": True, "banniere": "Maintenance dimanche gras & cie", "domaine_defaut": None, "ttl_secondes": 60} r = client.get("/recherche") assert r.status_code == 200 assert 'id="banniere-hub"' in r.text assert "Maintenance dimanche <b>gras</b> & cie" in r.text assert "kanbante_banniere_masquee" in r.text def test_banniere_absente_si_vide_ou_hub_muet(client, faux_hub): faux_hub["reglages"] = { "recherche_partout": True, "banniere": "", "domaine_defaut": "pro", "ttl_secondes": 60} r = client.get("/recherche") assert r.status_code == 200 assert "banniere-hub" not in r.text hub_reglages._reset_cache() faux_hub["mode"] = "panne" r = client.get("/recherche") assert r.status_code == 200 assert "banniere-hub" not in r.text def test_recherche_drapeau_rendu(client, faux_hub): faux_hub["reglages"] = { "recherche_partout": False, "banniere": "", "domaine_defaut": None, "ttl_secondes": 60} r = client.get("/recherche") assert r.status_code == 200 assert 'data-recherche-partout="false"' in r.text hub_reglages._reset_cache() faux_hub["reglages"] = { "recherche_partout": True, "banniere": "", "domaine_defaut": None, "ttl_secondes": 60} r = client.get("/recherche") assert 'data-recherche-partout="true"' in r.text hub_reglages._reset_cache() faux_hub["mode"] = "panne" # Hub muet → défaut actif r = client.get("/recherche") assert 'data-recherche-partout="true"' in r.text def test_version_0_46_3(): from pathlib import Path version = (Path(__file__).parent.parent / "VERSION").read_text().strip() assert version == "0.46.3"