From 9abd6c9d4d327313338cde3099daa19a1c09b552 Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 28 Oct 2026 12:11:10 -0400 Subject: [PATCH] Fusion #69 : Alertes unifiees (tests/test_draco_alertes.py) --- tests/test_draco_alertes.py | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/test_draco_alertes.py diff --git a/tests/test_draco_alertes.py b/tests/test_draco_alertes.py new file mode 100644 index 0000000..86c08f6 --- /dev/null +++ b/tests/test_draco_alertes.py @@ -0,0 +1,63 @@ +"""Fusion #69 — Alertes unifiées (chantier 2, Flowdeck). + +Source réelle de Flowdeck : GET /api/notifications (non lues = +actives, `url` relative pour le lien profond), déjà exposée et +inchangée ici. Couverts : placeholder Alertes disparu, module servi, +CSP connect-src avec les origines soeurs, CORS (origines soeurs +ajoutées à allow_origins ; tiers sans en-tête). +""" + +SOEURS = ("https://og.dracodev.net", "https://flowdeck.dracodev.net", + "https://tiktak.dracodev.net", "https://kanbante.dracodev.net") + + +def test_barre_alertes_sans_placeholder_et_module_servi(client): + # Les pages applicatives authentifiées ne sont pas atteignables + # telles quelles dans la sandbox (l'écran de connexion est servi + # pour "/" comme pour "/dashboard") : on vérifie donc le gabarit + # livré lui-même, plus les fichiers statiques servis. + from pathlib import Path + + gabarit = Path("app/templates/_draco_bar.html").read_text() + assert "Alertes unifiées : arrive avec la fédération" not in gabarit + assert "draco-alertes.js" in gabarit + assert "draco-alertes.css" in gabarit + assert "DRACO_ALERTES.open()" in gabarit + js = client.get("/static/js/draco-alertes.js") + assert js.status_code == 200 + assert "DRACO_ALERTES" in js.text + assert "api/notifications" in js.text + css = client.get("/static/css/draco-alertes.css") + assert css.status_code == 200 + assert "draco-alertes-overlay" in css.text + + +def test_csp_connect_src_contient_les_soeurs(client): + # La CSP est posée sur toute réponse HTML, y compris l'écran de + # connexion servi ici : elle prouve les origines soeurs en + # connect-src pour les pages de l'hôte. + r = client.get("/") + csp = r.headers.get("content-security-policy", "") + assert "connect-src" in csp + for origine in SOEURS: + assert origine in csp, csp + + +def test_notifications_api_et_cors_soeur(client): + r = client.get("/api/notifications") + assert r.status_code == 200 + body = r.json() + assert isinstance(body["notifications"], list) + assert "unread" in body + r2 = client.options("/api/notifications", + headers={"Origin": "https://tiktak.dracodev.net", + "Access-Control-Request-Method": "GET"}) + assert r2.headers.get("access-control-allow-origin") == "https://tiktak.dracodev.net" + assert r2.headers.get("access-control-allow-credentials") == "true" + r3 = client.get("/api/notifications", + headers={"Origin": "https://tiktak.dracodev.net"}) + assert r3.headers.get("access-control-allow-origin") == "https://tiktak.dracodev.net" + r4 = client.options("/api/notifications", + headers={"Origin": "https://evil.example", + "Access-Control-Request-Method": "GET"}) + assert "access-control-allow-origin" not in r4.headers