feat(api): CRUD des référentiels réservé aux administrateurs
L'écran d'administration du front proposait des boutons Modifier et Désactiver qui n'appelaient rien (console.log côté client), faute d'endpoints correspondants. Ajoute POST/PUT/DELETE sur /services, /categories, /contacts et /monitorings, tous protégés par require_admin. Deux garde-fous métier : - la suppression d'un service ou d'une catégorie est refusée (409) tant que des monitorings ou contacts y sont rattachés, avec le décompte dans le message, plutôt que de laisser remonter une violation de clé étrangère ; - un monitoring est désactivé (actif = 0) et jamais supprimé, car TABLE_FINAL référence son identifiant et l'historique doit rester consultable. Le rôle utilisateur est désormais typé par l'enum UserRole : Pydantic le valide seul (422), ce qui supprime les deux contrôles manuels dupliqués dans create_user et update_user. Tests : 8 -> 16. Couvre la validation par enum, le refus 409 sur rattachement, le 403 pour un non-administrateur et la désactivation logique du monitoring.
This commit is contained in:
@@ -66,6 +66,70 @@ def test_monitoring_unknown_returns_404(client, cur, auth_headers):
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_create_user_role_invalide_rejete(client, cur, auth_headers):
|
||||
# Le rôle est contraint par l'enum UserRole : Pydantic rejette en 422
|
||||
# avant d'atteindre la base, sans validation manuelle dans la route.
|
||||
r = client.post(
|
||||
"/admin/users",
|
||||
headers=auth_headers,
|
||||
json={"username": "x", "email": "x@x.fr", "password": "p", "role": "Root"},
|
||||
)
|
||||
assert r.status_code == 422
|
||||
|
||||
|
||||
def test_create_user_role_valide_accepte(client, cur, auth_headers):
|
||||
r = client.post(
|
||||
"/admin/users",
|
||||
headers=auth_headers,
|
||||
json={"username": "x", "email": "x@x.fr", "password": "p", "role": "Superviseur"},
|
||||
)
|
||||
assert r.status_code == 201
|
||||
|
||||
|
||||
def test_creation_service_reservee_admin(client, cur):
|
||||
# Un Consultant ne doit pas pouvoir écrire dans le référentiel.
|
||||
token = create_access_token({"sub": "bob", "role": "Consultant", "uid": 2})
|
||||
r = client.post(
|
||||
"/services",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
json={"nom_service": "Nouveau"},
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
|
||||
def test_creation_service_par_admin(client, cur, auth_headers):
|
||||
cur._one = (42,)
|
||||
r = client.post("/services", headers=auth_headers, json={"nom_service": "Comptabilité"})
|
||||
assert r.status_code == 201
|
||||
assert r.json() == {"id_service": 42, "nom_service": "Comptabilité"}
|
||||
|
||||
|
||||
def test_suppression_service_refusee_si_rattachements(client, cur, auth_headers):
|
||||
# 2 monitorings et 1 contact rattachés : la suppression doit être bloquée
|
||||
# explicitement plutôt que de laisser remonter une violation de clé étrangère.
|
||||
cur._one = (2, 1)
|
||||
r = client.delete("/services/1", headers=auth_headers)
|
||||
assert r.status_code == 409
|
||||
assert "2 monitoring(s)" in r.json()["detail"]
|
||||
|
||||
|
||||
def test_suppression_service_sans_rattachement(client, cur, auth_headers):
|
||||
cur._one = (0, 0)
|
||||
r = client.delete("/services/1", headers=auth_headers)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["status"] == "deleted"
|
||||
|
||||
|
||||
def test_desactivation_monitoring_ne_supprime_pas(client, cur, auth_headers):
|
||||
# TABLE_FINAL référence id_monito : on désactive, on ne supprime jamais.
|
||||
r = client.delete("/monitorings/1", headers=auth_headers)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["status"] == "deactivated"
|
||||
requetes = " ".join(q for q, _ in cur.executed)
|
||||
assert "UPDATE NOMENCLATURE_MONITO SET actif = 0" in requetes
|
||||
assert "DELETE FROM NOMENCLATURE_MONITO" not in requetes
|
||||
|
||||
|
||||
def test_admin_forbidden_for_consultant(client):
|
||||
token = create_access_token({"sub": "bob", "role": "Consultant", "uid": 2})
|
||||
r = client.get("/admin/users", headers={"Authorization": f"Bearer {token}"})
|
||||
|
||||
Reference in New Issue
Block a user