"""On-demand purge of a soft-deleted tree: permanent, owner-only, name-confirmed, and cascades to all tree data.""" import uuid from sqlalchemy import func, select from app.models.person import Person from app.models.tree import Tree from tests.conftest import auth, register async def _tree_with_person(client, owner): tid = (await client.post("/api/v1/trees", json={"name": "Purge Me"}, headers=owner)).json()["id"] await client.post( f"/api/v1/trees/{tid}/persons", json={"given": "Doomed", "surname": "Soul"}, headers=owner ) return tid async def test_purge_requires_soft_delete_first(client): owner = auth(await register(client, "purge-a@ex.com")) tid = await _tree_with_person(client, owner) # A live tree can't be purged — it must be trashed first. r = await client.post( f"/api/v1/trees/{tid}/purge", json={"confirm_name": "Purge Me"}, headers=owner ) assert r.status_code == 409 async def test_purge_name_must_match(client): owner = auth(await register(client, "purge-b@ex.com")) tid = await _tree_with_person(client, owner) await client.delete(f"/api/v1/trees/{tid}", headers=owner) # soft-delete r = await client.post( f"/api/v1/trees/{tid}/purge", json={"confirm_name": "WRONG"}, headers=owner ) assert r.status_code == 403 # Still in the trash — nothing destroyed. deleted = (await client.get("/api/v1/trees", params={"deleted": True}, headers=owner)).json() assert any(t["id"] == tid for t in deleted) async def test_purge_owner_only(client): owner = auth(await register(client, "purge-c@ex.com")) other = auth(await register(client, "purge-c2@ex.com")) tid = await _tree_with_person(client, owner) await client.delete(f"/api/v1/trees/{tid}", headers=owner) r = await client.post( f"/api/v1/trees/{tid}/purge", json={"confirm_name": "Purge Me"}, headers=other ) assert r.status_code in (403, 404) async def test_purge_removes_tree_and_cascades(client, db_session): owner = auth(await register(client, "purge-d@ex.com")) tid = await _tree_with_person(client, owner) await client.delete(f"/api/v1/trees/{tid}", headers=owner) r = await client.post( f"/api/v1/trees/{tid}/purge", json={"confirm_name": "Purge Me"}, headers=owner ) assert r.status_code == 204 # Gone from the trash... deleted = (await client.get("/api/v1/trees", params={"deleted": True}, headers=owner)).json() assert not any(t["id"] == tid for t in deleted) # ...and cascaded: no tree row, no person rows. tuuid = uuid.UUID(tid) assert ( await db_session.execute(select(func.count()).select_from(Tree).where(Tree.id == tuuid)) ).scalar() == 0 assert ( await db_session.execute( select(func.count()).select_from(Person).where(Person.tree_id == tuuid) ) ).scalar() == 0