"""Authed non-member reads must redact PER-PERSON, not just gate on the tree. A logged-in user who is NOT a member of a public tree previously saw living people's dates, real alternate names, and media through the family-view endpoints — only the person *list* was redacted. These tests assert that leak is closed while members still see everything. """ from tests.conftest import auth, register LSURNAME = "Authleaksurname" LALIAS = "Authleakalias" LYEAR = "2003" async def _setup(client): owner = auth(await register(client, "anm-owner@ex.com")) tid = ( await client.post( "/api/v1/trees", json={"name": "Pub", "visibility": "public"}, headers=owner ) ).json()["id"] old = ( await client.post( f"/api/v1/trees/{tid}/persons", json={"given": "Olde", "surname": "Gone", "is_living": False}, headers=owner, ) ).json()["id"] young = ( await client.post( f"/api/v1/trees/{tid}/persons", json={"given": "Youngauth", "surname": LSURNAME, "is_living": True}, headers=owner, ) ).json()["id"] for pid, year in ((old, "1855"), (young, LYEAR)): await client.post( f"/api/v1/trees/{tid}/events", json={"event_type": "birth", "person_id": pid, "date_value": year}, headers=owner, ) await client.post( f"/api/v1/trees/{tid}/persons/{young}/names", json={"name_type": "alias", "given": LALIAS}, headers=owner, ) om = ( await client.post( f"/api/v1/trees/{tid}/media", files={"file": ("o.txt", b"old-photo", "text/plain")}, data={"person_id": old}, headers=owner, ) ).json()["id"] ym = ( await client.post( f"/api/v1/trees/{tid}/media", files={"file": ("y.txt", b"young-photo", "text/plain")}, data={"person_id": young}, headers=owner, ) ).json()["id"] return owner, tid, old, young, om, ym async def test_authed_nonmember_does_not_see_living_pii(client): owner, tid, old, young, om, ym = await _setup(client) stranger = auth(await register(client, "anm-stranger@ex.com")) # Living person's events dropped; deceased kept. events = (await client.get(f"/api/v1/trees/{tid}/events", headers=stranger)).json() assert any(e["person_id"] == old for e in events) assert not any(e["person_id"] == young for e in events) # Per-person living: names + events empty. assert ( await client.get(f"/api/v1/trees/{tid}/persons/{young}/names", headers=stranger) ).json() == [] assert ( await client.get(f"/api/v1/trees/{tid}/persons/{young}/events", headers=stranger) ).json() == [] # The living surname/alias/birth-year must not appear in any of these. for path in ( f"/api/v1/trees/{tid}/events", f"/api/v1/trees/{tid}/relationships", f"/api/v1/trees/{tid}/persons/{young}/names", f"/api/v1/trees/{tid}/media", ): body = (await client.get(path, headers=stranger)).text assert LSURNAME not in body, path assert LALIAS not in body, path assert LYEAR not in body, path # Media: living person's media hidden from the list and undownloadable; # deceased person's media is fine. media_ids = {m["id"] for m in (await client.get(f"/api/v1/trees/{tid}/media", headers=stranger)).json()} assert om in media_ids assert ym not in media_ids assert ( await client.get(f"/api/v1/trees/{tid}/media/{ym}/content", headers=stranger) ).status_code == 404 assert ( await client.get(f"/api/v1/trees/{tid}/media/{om}/content", headers=stranger) ).status_code == 200 async def test_member_still_sees_everything(client): owner, tid, old, young, om, ym = await _setup(client) events = (await client.get(f"/api/v1/trees/{tid}/events", headers=owner)).json() assert any(e["person_id"] == young for e in events) assert ( await client.get(f"/api/v1/trees/{tid}/persons/{young}/names", headers=owner) ).json() != [] member_media = {m["id"] for m in (await client.get(f"/api/v1/trees/{tid}/media", headers=owner)).json()} assert ym in member_media assert ( await client.get(f"/api/v1/trees/{tid}/media/{ym}/content", headers=owner) ).status_code == 200