98 lines
3.8 KiB
Python
98 lines
3.8 KiB
Python
"""
|
|
Tests RGPD — droit à l'effacement (art. 17) : anonymisation d'un `sub`
|
|
OIDC dans toutes les tables qui le référencent (routes/rgpd.py,
|
|
services/rgpd_service.py, models/rgpd_model.py).
|
|
"""
|
|
|
|
from models import annonce_model, fichier_model, evenement_model, audit_model
|
|
from services import annonce_service, evenement_service, rgpd_service
|
|
|
|
|
|
def _seed_user_data(db, sub):
|
|
"""Crée une ligne dans chaque table référençant `sub`, y compris
|
|
audit_log (via les services, comme le ferait l'application réelle).
|
|
|
|
create_annonce() et create_evenement() journalisent déjà chacun une
|
|
ligne dans audit_log (CREATE_ANNONCE, CREATE_EVENEMENT) ; on ajoute un
|
|
LOGIN explicite pour obtenir 3 lignes audit_log au total pour ce sub.
|
|
"""
|
|
annonce_id = annonce_service.create_annonce(db, "Titre", "Contenu", sub)
|
|
fichier_model.insert(db, "doc.pdf", "uuid-stocke", "application/pdf", 10, sub)
|
|
evenement_service.create_evenement(
|
|
db, "Réunion", "desc", "2026-09-10T10:00:00", "2026-09-10T11:00:00", sub
|
|
)
|
|
from services import audit_service
|
|
|
|
audit_service.log(db, "LOGIN", None, "SUCCESS", sub, "127.0.0.1")
|
|
return annonce_id
|
|
|
|
|
|
# --- Accès -------------------------------------------------------------------
|
|
def test_rgpd01_sans_auth_401(client):
|
|
resp = client.delete("/api/admin/users/sub-cible/data")
|
|
assert resp.status_code == 401
|
|
|
|
|
|
def test_rgpd02_non_admin_403(user_session):
|
|
resp = user_session.delete("/api/admin/users/sub-cible/data")
|
|
assert resp.status_code == 403
|
|
|
|
|
|
def test_rgpd03_sub_inconnu_404(admin_session):
|
|
resp = admin_session.delete("/api/admin/users/sub-jamais-vu/data")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# --- Anonymisation effective ---------------------------------------------
|
|
def test_rgpd04_anonymisation_remplace_le_sub_partout(db, admin_session):
|
|
target_sub = "sub-a-effacer-0001"
|
|
annonce_id = _seed_user_data(db, target_sub)
|
|
|
|
resp = admin_session.delete(f"/api/admin/users/{target_sub}/data")
|
|
assert resp.status_code == 200
|
|
body = resp.get_json()
|
|
assert body["status"] == "anonymized"
|
|
pseudonym = body["pseudonym"]
|
|
assert pseudonym.startswith("anon:")
|
|
assert body["rows_updated"] == {
|
|
"annonces": 1, "fichiers": 1, "evenements": 1, "audit_log": 3,
|
|
}
|
|
|
|
# Le sub d'origine n'apparaît plus nulle part...
|
|
assert annonce_model.get(db, annonce_id)["auteur_sub"] == pseudonym
|
|
fichiers = fichier_model.fetch_all(db)
|
|
assert fichiers[0]["uploader_sub"] == pseudonym
|
|
evenements = evenement_model.fetch_all(db)
|
|
assert evenements[0]["createur_sub"] == pseudonym
|
|
|
|
recent = audit_model.fetch_recent(db)
|
|
subs_restants = [row["utilisateur_sub"] for row in recent]
|
|
assert target_sub not in subs_restants
|
|
|
|
# ... mais une entrée d'audit trace l'action d'anonymisation elle-même,
|
|
# journalisée sous l'identité de l'ADMIN qui l'a déclenchée (pas la
|
|
# cible, déjà pseudonymisée à ce stade).
|
|
actions = {row["action"] for row in recent}
|
|
assert "RGPD_ANONYMIZE_USER" in actions
|
|
|
|
|
|
def test_rgpd05_pseudonyme_deterministe_pour_un_meme_sub(db):
|
|
target_sub = "sub-deterministe-0002"
|
|
_seed_user_data(db, target_sub)
|
|
result = rgpd_service.anonymize_user(db, target_sub, pepper="pepper-de-test")
|
|
assert result["anonymized"] is True
|
|
# Toutes les tables ont reçu EXACTEMENT le même pseudonyme.
|
|
pseudo = result["pseudonym"]
|
|
assert annonce_model.fetch_all(db)[0]["auteur_sub"] == pseudo
|
|
assert fichier_model.fetch_all(db)[0]["uploader_sub"] == pseudo
|
|
|
|
|
|
def test_rgpd06_deja_anonymise_refuse(db, admin_session):
|
|
target_sub = "sub-double-appel-0003"
|
|
_seed_user_data(db, target_sub)
|
|
first = admin_session.delete(f"/api/admin/users/{target_sub}/data")
|
|
pseudonym = first.get_json()["pseudonym"]
|
|
|
|
second = admin_session.delete(f"/api/admin/users/{pseudonym}/data")
|
|
assert second.status_code == 400
|