"""Admins can permanently delete users with no activity footprint — anyone with tests/evidence/worklogs/etc must be deactivated instead, to keep the audit trail intact. """ import uuid import pytest @pytest.fixture def technique(client, auth_headers): response = client.post( "/api/v1/techniques", json={"mitre_id": "T1059", "name": "Test Technique"}, headers=auth_headers, ) return response.json() def test_admin_can_delete_user_with_no_footprint(api, auth_headers): created = api( "post", "/api/v1/users", auth_headers, json={"full_name": "Disposable User", "email": "disposable@test.com", "role": "viewer"}, ) assert created.status_code == 201, created.text user_id = created.json()["id"] resp = api("delete", f"/api/v1/users/{user_id}", auth_headers) assert resp.status_code == 204, resp.text get_resp = api("get", f"/api/v1/users/{user_id}", auth_headers) assert get_resp.status_code == 404 def test_admin_cannot_delete_own_account(api, auth_headers, admin_user): resp = api("delete", f"/api/v1/users/{admin_user.id}", auth_headers) assert resp.status_code == 400 assert "own account" in resp.text.lower() def test_admin_cannot_delete_user_with_test_footprint(api, auth_headers, technique, red_lead_headers, red_lead_user, client): client.cookies.clear() test_resp = api( "post", "/api/v1/tests", red_lead_headers, json={ "technique_id": technique["id"], "name": "Footprint Test", "description": "x", "platform": "windows", }, ) assert test_resp.status_code == 201, test_resp.text resp = api("delete", f"/api/v1/users/{red_lead_user.id}", auth_headers) assert resp.status_code == 400 assert "activity history" in resp.text.lower() or "deactivate" in resp.text.lower() # The user must still be there — deactivating is the only allowed path. get_resp = api("get", f"/api/v1/users/{red_lead_user.id}", auth_headers) assert get_resp.status_code == 200 def test_delete_user_requires_admin(api, red_lead_headers, red_lead_user): resp = api("delete", f"/api/v1/users/{red_lead_user.id}", red_lead_headers) assert resp.status_code == 403 def test_delete_user_not_found(api, auth_headers): resp = api("delete", f"/api/v1/users/{uuid.uuid4()}", auth_headers) assert resp.status_code == 404 def test_delete_cleans_up_notifications_and_tokens(api, auth_headers, db): created = api( "post", "/api/v1/users", auth_headers, json={"full_name": "Notifiable User", "email": "notifiable@test.com", "role": "viewer"}, ) user_id = uuid.UUID(created.json()["id"]) from app.models.notification import Notification from app.models.password_setup_token import PasswordSetupToken from app.services.notification_service import create_notification create_notification( db, user_id=user_id, type="test", title="Hi", message="hi", entity_type="test", entity_id=uuid.uuid4(), ) db.add(PasswordSetupToken(user_id=user_id, token="tok-abc", expires_at=__import__("datetime").datetime.utcnow())) db.commit() resp = api("delete", f"/api/v1/users/{user_id}", auth_headers) assert resp.status_code == 204, resp.text assert db.query(Notification).filter(Notification.user_id == user_id).count() == 0 assert db.query(PasswordSetupToken).filter(PasswordSetupToken.user_id == user_id).count() == 0