CI / test (push) Successful in 2m21s
La dérivation 0.32.1 ne couvrait que les URL sœurs depuis AGENT_EVENEMENTS_URL, jamais agent_url : sur la dev, le panneau visait le défaut prod (agent injoignable, prod non installée), console morte. Désormais, pour tout rendu à une requête (barre, iframe du panneau, CSP frame-src) : explicite > hôte public au motif fermé dracodev > dérivation élargie (événements ou base du service) > défaut officiel. Contextes sans requête inchangés sinon ; aucun SSO ajouté.
423 lines
16 KiB
Python
423 lines
16 KiB
Python
"""Fusion ACC-1 — barre d'apps commune (carte kanbante#28).
|
|
|
|
Le snippet figé de dracodev-commons est vendangé (CSS/JS statiques +
|
|
partiel _draco_bar.html) et inclus par le cadre partagé _header.html :
|
|
toutes les pages applicatives l'affichent avec Kanbante comme app
|
|
courante ; l'écran de connexion, autonome, ne l'affiche pas.
|
|
"""
|
|
|
|
|
|
def test_barre_presente_sur_le_dashboard(client):
|
|
r = client.get("/")
|
|
assert r.status_code == 200
|
|
assert 'class="draco-bar"' in r.text
|
|
assert 'data-current="kanbante"' in r.text
|
|
assert "draco-barre.css" in r.text
|
|
assert "draco-barre.js" in r.text
|
|
assert 'window.DRACO_BAR_CONFIG' in r.text
|
|
# Les quatre applications de la barre, aux URL publiques par
|
|
# défaut (noms officiels/prod, issus de la configuration).
|
|
for url in ("https://og.dracodev.net/", "https://flowdeck.dracodev.net/",
|
|
"https://tiktak.dracodev.net/", "https://kanbante.dracodev.net/"):
|
|
assert url in r.text
|
|
# L'adresse du service d'agents est injectée au panneau.
|
|
assert 'window.DRACO_PANNEAU_CONFIG = { api: "https://agent.dracodev.net" }' in r.text
|
|
|
|
|
|
def test_barre_et_panneau_urls_par_configuration(monkeypatch, fake_gitea):
|
|
# Dev/prod (0.32.0) : la dev déclare ses noms en -dev par
|
|
# l'environnement ; aucun nom public n'est codé en dur.
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
|
|
monkeypatch.setenv("DRACO_SSO_URL", "https://og-dev.dracodev.net")
|
|
monkeypatch.setenv("DRACO_FLOWDECK_URL", "https://flowdeck-dev.dracodev.net")
|
|
monkeypatch.setenv("DRACO_TIKTAK_URL", "https://tiktak-dev.dracodev.net")
|
|
monkeypatch.setenv("KANBANTE_PUBLIC_URL", "https://kanbante-dev.dracodev.net")
|
|
monkeypatch.setenv("AGENT_URL", "https://agent-dev.dracodev.net")
|
|
with TestClient(main.app) as client:
|
|
r = client.get("/")
|
|
assert r.status_code == 200
|
|
for url in ("https://og-dev.dracodev.net/",
|
|
"https://flowdeck-dev.dracodev.net/",
|
|
"https://tiktak-dev.dracodev.net/",
|
|
"https://kanbante-dev.dracodev.net/"):
|
|
assert url in r.text
|
|
assert 'api: "https://agent-dev.dracodev.net"' in r.text
|
|
# Le JS statique du panneau ne contient aucun nom en dur :
|
|
# il lit l'adresse injectée par l'hôte.
|
|
js = client.get("/static/js/draco-panneau.js").text
|
|
assert "DRACO_PANNEAU_CONFIG" in js
|
|
assert "dracodev.net" not in js
|
|
|
|
|
|
def test_config_agent_evenements_derive_de_agent_url(monkeypatch):
|
|
from app.config import _env_settings
|
|
|
|
monkeypatch.setenv("AGENT_URL", "https://agent-dev.dracodev.net")
|
|
monkeypatch.delenv("AGENT_EVENEMENTS_URL", raising=False)
|
|
s = _env_settings()
|
|
assert s.agent_url == "https://agent-dev.dracodev.net"
|
|
assert s.agent_evenements_url == (
|
|
"https://agent-dev.dracodev.net/api/v1/evenements"
|
|
)
|
|
# L'URL d'événements explicite garde la priorité (override compose).
|
|
monkeypatch.setenv(
|
|
"AGENT_EVENEMENTS_URL", "https://exemple.invalid/journal"
|
|
)
|
|
assert _env_settings().agent_evenements_url == (
|
|
"https://exemple.invalid/journal"
|
|
)
|
|
|
|
|
|
# ── Dérivation d'environnement des URL sœurs (0.32.1) ──
|
|
# Sur la dev, seul AGENT_EVENEMENTS_URL est posé : les URL sœurs
|
|
# doivent suivre son suffixe (-dev) sans quatre variables de plus.
|
|
_ENV_URLS_SOEURS = (
|
|
"DRACO_SSO_URL", # ObsiGate (émettrice SSO)
|
|
"DRACO_FLOWDECK_URL",
|
|
"DRACO_TIKTAK_URL",
|
|
"KANBANTE_PUBLIC_URL", # Kanbante elle-même
|
|
"AGENT_URL",
|
|
)
|
|
|
|
_DEFAUTS_OFFICIELS = (
|
|
"https://og.dracodev.net",
|
|
"https://flowdeck.dracodev.net",
|
|
"https://tiktak.dracodev.net",
|
|
"https://kanbante.dracodev.net",
|
|
)
|
|
|
|
_URLS_DEV = (
|
|
"https://og-dev.dracodev.net",
|
|
"https://flowdeck-dev.dracodev.net",
|
|
"https://tiktak-dev.dracodev.net",
|
|
"https://kanbante-dev.dracodev.net",
|
|
)
|
|
|
|
|
|
def _nettoyer_env_soeurs(monkeypatch):
|
|
for nom in _ENV_URLS_SOEURS:
|
|
monkeypatch.delenv(nom, raising=False)
|
|
|
|
|
|
def _urls_soeurs(settings):
|
|
return (
|
|
settings.sso_url,
|
|
settings.flowdeck_url,
|
|
settings.tiktak_url,
|
|
settings.public_url,
|
|
)
|
|
|
|
|
|
def test_config_urls_soeurs_derivees_du_seul_agent_evenements(monkeypatch):
|
|
from app.config import _env_settings
|
|
|
|
_nettoyer_env_soeurs(monkeypatch)
|
|
monkeypatch.setenv(
|
|
"AGENT_EVENEMENTS_URL",
|
|
"https://agent-dev.dracodev.net/api/v1/evenements",
|
|
)
|
|
assert _urls_soeurs(_env_settings()) == _URLS_DEV
|
|
|
|
|
|
def test_barre_urls_soeurs_derivees_du_seul_agent_evenements(
|
|
monkeypatch, fake_gitea
|
|
):
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
|
|
_nettoyer_env_soeurs(monkeypatch)
|
|
monkeypatch.setenv(
|
|
"AGENT_EVENEMENTS_URL",
|
|
"https://agent-dev.dracodev.net/api/v1/evenements",
|
|
)
|
|
with TestClient(main.app) as client:
|
|
r = client.get("/")
|
|
assert r.status_code == 200
|
|
for url in _URLS_DEV:
|
|
assert f"{url}/" in r.text
|
|
|
|
|
|
def test_config_url_soeur_explicite_gagne_sur_la_derivation(monkeypatch):
|
|
from app.config import _env_settings
|
|
|
|
_nettoyer_env_soeurs(monkeypatch)
|
|
monkeypatch.setenv(
|
|
"AGENT_EVENEMENTS_URL",
|
|
"https://agent-dev.dracodev.net/api/v1/evenements",
|
|
)
|
|
monkeypatch.setenv(
|
|
"DRACO_FLOWDECK_URL", "https://flowdeck.example.test"
|
|
)
|
|
s = _env_settings()
|
|
assert s.flowdeck_url == "https://flowdeck.example.test"
|
|
# Les autres sœurs restent dérivées de l'environnement.
|
|
assert s.sso_url == "https://og-dev.dracodev.net"
|
|
assert s.tiktak_url == "https://tiktak-dev.dracodev.net"
|
|
assert s.public_url == "https://kanbante-dev.dracodev.net"
|
|
|
|
|
|
def test_config_hote_agent_hors_motif_reste_aux_defauts(monkeypatch):
|
|
from app.config import _env_settings
|
|
|
|
_nettoyer_env_soeurs(monkeypatch)
|
|
for url_agent in (
|
|
"https://agent.example.com/api/v1/evenements",
|
|
"https://agent-dev.autre-domaine.net/api/v1/evenements",
|
|
"http://192.168.30.101:8790/api/v1/evenements",
|
|
"http://127.0.0.1:8790/api/v1/evenements",
|
|
):
|
|
monkeypatch.setenv("AGENT_EVENEMENTS_URL", url_agent)
|
|
assert _urls_soeurs(_env_settings()) == _DEFAUTS_OFFICIELS
|
|
|
|
|
|
def test_config_aucun_env_reste_aux_defauts_officiels(monkeypatch):
|
|
from app.config import _env_settings
|
|
|
|
_nettoyer_env_soeurs(monkeypatch)
|
|
monkeypatch.delenv("AGENT_EVENEMENTS_URL", raising=False)
|
|
assert _urls_soeurs(_env_settings()) == _DEFAUTS_OFFICIELS
|
|
|
|
|
|
def test_statiques_de_la_barre_servis(client):
|
|
assert client.get("/static/css/draco-barre.css").status_code == 200
|
|
assert client.get("/static/js/draco-barre.js").status_code == 200
|
|
|
|
|
|
def test_menu_des_applications_par_la_marque(client):
|
|
# v0.1.3 : la marque ✦ porte data-act="apps" et le JS commun
|
|
# construit le menu (.draco-apps-menu) ; le logo passe à 24 px.
|
|
accueil = client.get("/")
|
|
assert 'data-act="apps"' in accueil.text
|
|
# 0.23.3 : assets versionnés, sinon le cache garde l'ancien JS.
|
|
assert "draco-barre.css?v=" in accueil.text
|
|
assert "draco-barre.js?v=" in accueil.text
|
|
js = client.get("/static/js/draco-barre.js").text
|
|
assert "draco-apps-menu" in js
|
|
css = client.get("/static/css/draco-barre.css").text
|
|
assert "font-size: 24px" in css
|
|
|
|
|
|
def test_barre_pleine_largeur_et_palier_mobile(client):
|
|
# 0.23.1 : la barre est fixe pleine largeur (le padding du cadre
|
|
# ne la repousse plus) et le palier mobile commons v0.1.2 ne
|
|
# garde que l'onglet de l'app courante.
|
|
style = client.get("/static/css/style.css").text
|
|
assert "body.has-draco-bar .draco-bar" in style
|
|
assert "position: fixed" in style
|
|
barre = client.get("/static/css/draco-barre.css").text
|
|
assert '.draco-bar[data-current="kanbante"] .draco-apptab[data-app="kanbante"]' in barre
|
|
|
|
|
|
def test_pas_de_barre_sur_le_login(monkeypatch, fake_gitea):
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
|
|
monkeypatch.setenv("KANBANTE_AUTH_USER", "bruno")
|
|
monkeypatch.setenv("KANBANTE_AUTH_PASSWORD", "motdepasse")
|
|
with TestClient(main.app) as client:
|
|
r = client.get("/login")
|
|
assert r.status_code == 200
|
|
assert "draco-bar" not in r.text
|
|
|
|
|
|
# ── Résolution par requête et base de l'agent dérivée (0.32.2) ──
|
|
# Couche fautive corrigée : la dérivation 0.32.1 ne couvrait que les
|
|
# URL sœurs depuis AGENT_EVENEMENTS_URL — jamais agent_url, donc le
|
|
# panneau visait le défaut prod (agent injoignable : la prod n'est
|
|
# pas installée) et la console était une page morte sur la dev.
|
|
# Aucune résolution ne tenait compte de l'hôte de la requête, et
|
|
# aucune CSP n'énonçait l'origine de l'agent.
|
|
_ENV_RESOLUTION = _ENV_URLS_SOEURS + ("AGENT_EVENEMENTS_URL",)
|
|
|
|
|
|
def _nettoyer_env_resolution(monkeypatch):
|
|
for nom in _ENV_RESOLUTION:
|
|
monkeypatch.delenv(nom, raising=False)
|
|
|
|
|
|
def test_config_base_agent_derivee_du_seul_agent_evenements(monkeypatch):
|
|
# Le cas réel de la dev : seul le journal est déclaré en -dev.
|
|
# La base du service (panneau/console) doit suivre, sinon
|
|
# l'iframe de la console charge l'agent prod, injoignable.
|
|
from app.config import _env_settings
|
|
|
|
_nettoyer_env_resolution(monkeypatch)
|
|
monkeypatch.setenv(
|
|
"AGENT_EVENEMENTS_URL",
|
|
"https://agent-dev.dracodev.net/api/v1/evenements",
|
|
)
|
|
s = _env_settings()
|
|
assert s.agent_url == "https://agent-dev.dracodev.net"
|
|
assert _urls_soeurs(s) == _URLS_DEV
|
|
|
|
|
|
def test_config_derivation_elargie_a_la_base_du_service(monkeypatch):
|
|
# Élargie : si l'URL d'événements est hors motif mais que la
|
|
# base du service suit le motif, les sœurs dérivent de la base.
|
|
from app.config import _env_settings
|
|
|
|
_nettoyer_env_resolution(monkeypatch)
|
|
monkeypatch.setenv("AGENT_URL", "https://agent-dev.dracodev.net")
|
|
monkeypatch.setenv(
|
|
"AGENT_EVENEMENTS_URL", "https://journal.example.test/evenements"
|
|
)
|
|
s = _env_settings()
|
|
assert s.agent_url == "https://agent-dev.dracodev.net"
|
|
assert s.agent_evenements_url == (
|
|
"https://journal.example.test/evenements"
|
|
)
|
|
assert _urls_soeurs(s) == _URLS_DEV
|
|
|
|
|
|
def test_suffixe_environnement_requete_motif_ferme():
|
|
from app.config import suffixe_environnement_requete
|
|
|
|
assert suffixe_environnement_requete("kanbante-dev.dracodev.net") == "-dev"
|
|
assert suffixe_environnement_requete("kanbante.dracodev.net") == ""
|
|
assert suffixe_environnement_requete("og-dev.dracodev.net") == "-dev"
|
|
assert suffixe_environnement_requete("agent-dev.dracodev.net:8790") == "-dev"
|
|
assert suffixe_environnement_requete("Kanbante-Dev.Dracodev.Net") == "-dev"
|
|
# Hors motif : jamais de suffixe inventé depuis un hôte arbitraire.
|
|
assert suffixe_environnement_requete("192.168.30.101:8082") is None
|
|
assert suffixe_environnement_requete("127.0.0.1") is None
|
|
assert suffixe_environnement_requete("kanbante-dev.autre-domaine.net") is None
|
|
assert suffixe_environnement_requete("dev.dracodev.net") is None
|
|
assert suffixe_environnement_requete("testserver") is None
|
|
assert suffixe_environnement_requete("") is None
|
|
assert suffixe_environnement_requete(None) is None
|
|
|
|
|
|
def test_requete_hote_dev_sans_env_tout_en_dev(monkeypatch, fake_gitea):
|
|
# Aucune variable d'environnement : barre, panneau (source de
|
|
# l'iframe de la console) et CSP entièrement en -dev, cohérents.
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
|
|
_nettoyer_env_resolution(monkeypatch)
|
|
with TestClient(main.app) as client:
|
|
r = client.get("/", headers={"Host": "kanbante-dev.dracodev.net"})
|
|
assert r.status_code == 200
|
|
for url in _URLS_DEV:
|
|
assert f"{url}/" in r.text
|
|
# Source de l'iframe du panneau : l'origine injectée au JS.
|
|
assert 'api: "https://agent-dev.dracodev.net"' in r.text
|
|
# La CSP de la même réponse énonce la même origine résolue.
|
|
assert r.headers["content-security-policy"] == (
|
|
"frame-src 'self' https://agent-dev.dracodev.net"
|
|
)
|
|
# Aucun nom prod ne subsiste dans le rendu.
|
|
for url in _DEFAUTS_OFFICIELS:
|
|
assert f"{url}/" not in r.text
|
|
assert 'api: "https://agent.dracodev.net"' not in r.text
|
|
|
|
|
|
def test_requete_hote_prod_sans_env_noms_officiels(monkeypatch, fake_gitea):
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
|
|
_nettoyer_env_resolution(monkeypatch)
|
|
with TestClient(main.app) as client:
|
|
r = client.get("/", headers={"Host": "kanbante.dracodev.net"})
|
|
assert r.status_code == 200
|
|
for url in _DEFAUTS_OFFICIELS:
|
|
assert f"{url}/" in r.text
|
|
assert 'api: "https://agent.dracodev.net"' in r.text
|
|
assert r.headers["content-security-policy"] == (
|
|
"frame-src 'self' https://agent.dracodev.net"
|
|
)
|
|
|
|
|
|
def test_requete_hote_lan_repli_configuration(monkeypatch, fake_gitea):
|
|
# Hôte hors motif (LAN/IP) : repli sur la configuration, ici
|
|
# sans variable — donc les défauts officiels, jamais devinés.
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
|
|
_nettoyer_env_resolution(monkeypatch)
|
|
with TestClient(main.app) as client:
|
|
r = client.get("/", headers={"Host": "192.168.30.101:8082"})
|
|
assert r.status_code == 200
|
|
for url in _DEFAUTS_OFFICIELS:
|
|
assert f"{url}/" in r.text
|
|
assert 'api: "https://agent.dracodev.net"' in r.text
|
|
assert r.headers["content-security-policy"] == (
|
|
"frame-src 'self' https://agent.dracodev.net"
|
|
)
|
|
|
|
|
|
def test_requete_hote_lan_repli_derivation_configuration(
|
|
monkeypatch, fake_gitea
|
|
):
|
|
# Hôte hors motif mais configuration en -dev (journal seul) :
|
|
# le repli est la dérivation de configuration, panneau inclus.
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
|
|
_nettoyer_env_resolution(monkeypatch)
|
|
monkeypatch.setenv(
|
|
"AGENT_EVENEMENTS_URL",
|
|
"https://agent-dev.dracodev.net/api/v1/evenements",
|
|
)
|
|
with TestClient(main.app) as client:
|
|
r = client.get("/", headers={"Host": "192.168.30.101:8082"})
|
|
assert r.status_code == 200
|
|
for url in _URLS_DEV:
|
|
assert f"{url}/" in r.text
|
|
assert 'api: "https://agent-dev.dracodev.net"' in r.text
|
|
assert r.headers["content-security-policy"] == (
|
|
"frame-src 'self' https://agent-dev.dracodev.net"
|
|
)
|
|
|
|
|
|
def test_requete_explicite_gagne_sur_hote(monkeypatch, fake_gitea):
|
|
# L'explicite reste prioritaire, URL par URL : Flowdeck et
|
|
# l'agent explicites gagnent sur l'hôte dev ; les autres
|
|
# sœurs suivent l'hôte.
|
|
from fastapi.testclient import TestClient
|
|
|
|
import app.main as main
|
|
|
|
_nettoyer_env_resolution(monkeypatch)
|
|
monkeypatch.setenv("DRACO_FLOWDECK_URL", "https://flowdeck.example.test")
|
|
monkeypatch.setenv("AGENT_URL", "https://agent.example.test")
|
|
with TestClient(main.app) as client:
|
|
r = client.get("/", headers={"Host": "kanbante-dev.dracodev.net"})
|
|
assert r.status_code == 200
|
|
assert "https://flowdeck.example.test/" in r.text
|
|
assert "https://flowdeck-dev.dracodev.net/" not in r.text
|
|
assert 'api: "https://agent.example.test"' in r.text
|
|
# La CSP consomme l'origine explicite, comme l'iframe.
|
|
assert r.headers["content-security-policy"] == (
|
|
"frame-src 'self' https://agent.example.test"
|
|
)
|
|
for url in (
|
|
"https://og-dev.dracodev.net/",
|
|
"https://tiktak-dev.dracodev.net/",
|
|
"https://kanbante-dev.dracodev.net/",
|
|
):
|
|
assert url in r.text
|
|
|
|
|
|
def test_resolution_sans_requete_inchangee(monkeypatch):
|
|
# Contextes sans requête : la résolution par configuration
|
|
# seule, comme avant (défauts officiels sans environnement).
|
|
from app.config import _env_settings, resoudre_urls_draco
|
|
|
|
_nettoyer_env_resolution(monkeypatch)
|
|
urls = resoudre_urls_draco()
|
|
assert urls.sso_url == "https://og.dracodev.net"
|
|
assert urls.flowdeck_url == "https://flowdeck.dracodev.net"
|
|
assert urls.tiktak_url == "https://tiktak.dracodev.net"
|
|
assert urls.public_url == "https://kanbante.dracodev.net"
|
|
assert urls.agent_url == "https://agent.dracodev.net"
|
|
assert _env_settings().agent_url == "https://agent.dracodev.net"
|