Trois domaines n'étaient pas couverts : l'historique et les courbes d'évolution, les contrôles de sécurité, et le CRUD des référentiels. - test_historique.py : bornes de dates passées en paramètres, rejet d'un format de date invalide, agrégation des séries par monitoring et par service à partir de lignes à plat. - test_securite.py : protection 401 vérifiée route par route, RBAC 403 pour Superviseur et Consultant sur chaque écriture, rejet d'un jeton signé avec une autre clé, en-têtes de sécurité, rate-limit 429 à la 6e tentative, refus d'un compte désactivé, absence du hachage dans la réponse de login, anonymisation RGPD sans suppression de ligne. - test_referentiels.py : CRUD catégories et contacts, refus 409 sur rattachement, 404 sur enregistrement inexistant, modification partielle limitée aux champs fournis, réactivation d'un monitoring, plafonnement de limit, terme de recherche transmis en paramètre et non concaténé. Corrige au passage un défaut d'isolation révélé par la suite complète : le limiteur de débit est un état global, les tests de connexion se comptabilisaient entre eux et un test échouait selon l'ordre d'exécution tout en passant fichier par fichier. La fixture limiteur_vierge le remet à zéro, et un test dédié vérifie désormais explicitement le seuil. Suite vérifiée stable sur 3 exécutions consécutives et fichier par fichier.
144 lines
5.3 KiB
Python
144 lines
5.3 KiB
Python
"""CRUD des référentiels : catégories, contacts et nomenclature."""
|
|
|
|
|
|
def _requetes(cur) -> str:
|
|
return " ".join(q for q, _ in cur.executed)
|
|
|
|
|
|
def _parametres(cur) -> list:
|
|
return [p for _, args in cur.executed for p in args]
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# Catégories
|
|
# ------------------------------------------------------------
|
|
|
|
def test_creation_categorie(client, cur, auth_headers):
|
|
cur._one = (7,)
|
|
r = client.post("/categories", headers=auth_headers,
|
|
json={"intitule_categorie": "Litiges"})
|
|
assert r.status_code == 201
|
|
assert r.json() == {"id_categorie": 7, "intitule_categorie": "Litiges"}
|
|
|
|
|
|
def test_categorie_intitule_vide_refuse(client, cur, auth_headers):
|
|
# Contrainte min_length : on ne crée pas un référentiel sans libellé.
|
|
r = client.post("/categories", headers=auth_headers, json={"intitule_categorie": ""})
|
|
assert r.status_code == 422
|
|
|
|
|
|
def test_suppression_categorie_refusee_si_monitorings_rattaches(client, cur, auth_headers):
|
|
cur._one = (3,)
|
|
r = client.delete("/categories/1", headers=auth_headers)
|
|
assert r.status_code == 409
|
|
assert "3 monitoring(s)" in r.json()["detail"]
|
|
# Aucune suppression ne doit avoir été tentée.
|
|
assert "DELETE FROM CATEGORIE" not in _requetes(cur)
|
|
|
|
|
|
def test_suppression_categorie_libre_autorisee(client, cur, auth_headers):
|
|
cur._one = (0,)
|
|
r = client.delete("/categories/1", headers=auth_headers)
|
|
assert r.status_code == 200
|
|
assert "DELETE FROM CATEGORIE" in _requetes(cur)
|
|
|
|
|
|
def test_modification_categorie_inexistante_renvoie_404(client, cur, auth_headers):
|
|
cur.rowcount = 0
|
|
r = client.put("/categories/999", headers=auth_headers,
|
|
json={"intitule_categorie": "X"})
|
|
assert r.status_code == 404
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# Contacts
|
|
# ------------------------------------------------------------
|
|
|
|
def test_creation_contact(client, cur, auth_headers):
|
|
cur._one = (12,)
|
|
r = client.post("/contacts", headers=auth_headers, json={
|
|
"id_service": 1, "intitule_contact": "Référent",
|
|
"nom": "Dupont", "prenom": "Marie", "mail": "marie@xefi-fictif.fr",
|
|
})
|
|
assert r.status_code == 201
|
|
assert r.json()["id_contact"] == 12
|
|
assert r.json()["nom"] == "Dupont"
|
|
|
|
|
|
def test_contact_champ_manquant_refuse(client, cur, auth_headers):
|
|
r = client.post("/contacts", headers=auth_headers, json={"nom": "Dupont"})
|
|
assert r.status_code == 422
|
|
|
|
|
|
def test_suppression_contact_directe(client, cur, auth_headers):
|
|
# Aucune table ne référence un contact : suppression sans garde-fou.
|
|
r = client.delete("/contacts/1", headers=auth_headers)
|
|
assert r.status_code == 200
|
|
assert "DELETE FROM CONTACT" in _requetes(cur)
|
|
|
|
|
|
def test_suppression_contact_inexistant_renvoie_404(client, cur, auth_headers):
|
|
cur.rowcount = 0
|
|
assert client.delete("/contacts/999", headers=auth_headers).status_code == 404
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# Nomenclature des monitorings
|
|
# ------------------------------------------------------------
|
|
|
|
def test_creation_monitoring_actif_par_defaut(client, cur, auth_headers):
|
|
cur._one = (8,)
|
|
r = client.post("/monitorings", headers=auth_headers, json={
|
|
"monito_intitule": "Nouveau", "id_service": 1, "id_categorie": 1,
|
|
"table_source": "MONITO_TEST", "bdd_source": "Sage",
|
|
})
|
|
assert r.status_code == 201
|
|
assert "actif" in _requetes(cur)
|
|
|
|
|
|
def test_modification_partielle_ne_touche_que_les_champs_fournis(client, cur, auth_headers):
|
|
r = client.put("/monitorings/1", headers=auth_headers,
|
|
json={"monito_intitule": "Renommé"})
|
|
assert r.status_code == 200
|
|
requete = _requetes(cur)
|
|
assert "monito_intitule = ?" in requete
|
|
assert "bdd_source = ?" not in requete
|
|
|
|
|
|
def test_modification_sans_aucun_champ_refusee(client, cur, auth_headers):
|
|
r = client.put("/monitorings/1", headers=auth_headers, json={})
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_reactivation_possible_par_modification(client, cur, auth_headers):
|
|
# Permet de revenir sur une désactivation.
|
|
r = client.put("/monitorings/7", headers=auth_headers, json={"actif": True})
|
|
assert r.status_code == 200
|
|
assert "actif = ?" in _requetes(cur)
|
|
assert 1 in _parametres(cur)
|
|
|
|
|
|
def test_desactivation_monitoring_inexistant_renvoie_404(client, cur, auth_headers):
|
|
cur.rowcount = 0
|
|
assert client.delete("/monitorings/999", headers=auth_headers).status_code == 404
|
|
|
|
|
|
def test_details_monitoring_recherche_sur_les_colonnes_declarees(client, cur, auth_headers):
|
|
cur.description = [("id_ligne",)]
|
|
cur._rows = []
|
|
r = client.get("/monitorings/1/details", params={"search": "Lyon"}, headers=auth_headers)
|
|
assert r.status_code == 200
|
|
requete = _requetes(cur)
|
|
assert "LIKE ?" in requete
|
|
# Le terme est passé en paramètre, jamais injecté dans la requête.
|
|
assert "%Lyon%" in _parametres(cur)
|
|
assert "Lyon" not in requete
|
|
|
|
|
|
def test_details_monitoring_limite_bornee(client, cur, auth_headers):
|
|
# limit est plafonné à 5000 pour éviter un export non maîtrisé.
|
|
assert client.get("/monitorings/1/details", params={"limit": 99999},
|
|
headers=auth_headers).status_code == 422
|
|
assert client.get("/monitorings/1/details", params={"limit": 0},
|
|
headers=auth_headers).status_code == 422
|