"""Deleting a whitelist / access-request entry (admin). Deny keeps the row so a refused person cannot immediately re-ask; delete forgets the address outright, for an approval added by mistake or one that is no longer wanted. The one hard rule is that an entry backed by an account is NOT deletable here: `is_allowed` reads this row, so removing it would silently revoke a live user's access. """ from sqlalchemy import select from app.models import Invite, User def _invites(client): r = client.get("/api/admin/invites") assert r.status_code == 200 return {i["email"]: i for i in r.json()} def test_admin_deletes_a_whitelist_entry_that_never_became_an_account(db, make_user, as_user): admin = make_user(email="admin@example.com", role="admin") db.add(Invite(email="mistake@example.com", status="approved", decided_by=admin.email)) db.commit() client = as_user(admin) listed = _invites(client)["mistake@example.com"] assert listed["has_account"] is False # drives the delete affordance in the UI r = client.delete(f"/api/admin/invites/{listed['id']}") assert r.status_code == 200 and r.json() == {"deleted": listed["id"]} db.expire_all() assert ( db.execute(select(Invite).where(Invite.email == "mistake@example.com")).scalar_one_or_none() is None ) def test_deleting_an_entry_that_has_an_account_is_refused(db, make_user, as_user): # The guard that matters: this row is what is_allowed() reads, so deleting it would revoke the # user's access without touching the account or saying so. admin = make_user(email="admin@example.com", role="admin") member = make_user(email="member@example.com") db.add(Invite(email=member.email, status="approved", decided_by=admin.email)) db.commit() client = as_user(admin) listed = _invites(client)[member.email] assert listed["has_account"] is True r = client.delete(f"/api/admin/invites/{listed['id']}") assert r.status_code == 409 db.expire_all() assert ( db.execute(select(Invite).where(Invite.email == member.email)).scalar_one_or_none() is not None ) def test_deleting_a_pending_request_forgets_it(db, make_user, as_user): # A pending row is deletable too — "not now, and don't keep the record" is a different decision # from Deny, which keeps the row precisely so the person cannot immediately re-ask. admin = make_user(email="admin@example.com", role="admin") db.add(Invite(email="asker@example.com", status="pending")) db.commit() client = as_user(admin) r = client.delete(f"/api/admin/invites/{_invites(client)['asker@example.com']['id']}") assert r.status_code == 200 assert "asker@example.com" not in _invites(client) def test_deleting_a_missing_invite_is_404(db, make_user, as_user): admin = make_user(email="admin@example.com", role="admin") assert as_user(admin).delete("/api/admin/invites/9999").status_code == 404 def test_a_regular_user_cannot_delete_an_invite(db, make_user, as_user): make_user(email="admin@example.com", role="admin") plain = make_user(email="plain@example.com") db.add(Invite(email="mistake@example.com", status="approved")) db.commit() inv_id = db.execute(select(Invite.id).where(Invite.email == "mistake@example.com")).scalar_one() assert as_user(plain).delete(f"/api/admin/invites/{inv_id}").status_code == 403 db.expire_all() assert db.get(Invite, inv_id) is not None def test_deleting_the_user_also_clears_the_way_to_re_invite(db, make_user, as_user): # The refusal above tells the admin to delete the account instead; purge_user takes the invite # with it, so the address really is forgotten and a fresh request starts over as pending. admin = make_user(email="admin@example.com", role="admin") member = make_user(email="member@example.com") db.add(Invite(email=member.email, status="approved", decided_by=admin.email)) db.commit() client = as_user(admin) assert client.delete(f"/api/admin/users/{member.id}").status_code == 200 db.expire_all() assert db.execute(select(User).where(User.email == member.email)).scalar_one_or_none() is None assert ( db.execute(select(Invite).where(Invite.email == member.email)).scalar_one_or_none() is None )