"""Tree membership management: list, add-by-email, role change, remove, guards.""" from tests.conftest import auth, register async def test_membership_management(client): owner = auth(await register(client, "mm-owner@ex.com")) ed = auth(await register(client, "mm-editor@ex.com")) tid = (await client.post("/api/v1/trees", json={"name": "Fam"}, headers=owner)).json()["id"] # A non-member can't even see the member list of a private tree. assert (await client.get(f"/api/v1/trees/{tid}/members", headers=ed)).status_code == 403 # Add a non-existent user → 404. assert ( await client.post( f"/api/v1/trees/{tid}/members", json={"email": "ghost@ex.com", "role": "editor"}, headers=owner, ) ).status_code == 404 # Add the editor by email. r = await client.post( f"/api/v1/trees/{tid}/members", json={"email": "mm-editor@ex.com", "role": "editor"}, headers=owner, ) assert r.status_code == 201, r.text mid = r.json()["id"] assert r.json()["email"] == "mm-editor@ex.com" and r.json()["role"] == "editor" # Adding the same user again → 409. assert ( await client.post( f"/api/v1/trees/{tid}/members", json={"email": "mm-editor@ex.com", "role": "viewer"}, headers=owner, ) ).status_code == 409 # The editor can now see the tree's member list (2 members)... ml = (await client.get(f"/api/v1/trees/{tid}/members", headers=ed)).json() assert len(ml) == 2 owner_mid = next(m["id"] for m in ml if m["role"] == "owner") # ...but a non-owner can't manage members. assert ( await client.post( f"/api/v1/trees/{tid}/members", json={"email": "mm-owner@ex.com", "role": "viewer"}, headers=ed, ) ).status_code == 403 # Owner changes the editor's role. pr = await client.patch( f"/api/v1/trees/{tid}/members/{mid}", json={"role": "viewer"}, headers=owner ) assert pr.status_code == 200 and pr.json()["role"] == "viewer" # The sole owner can't be demoted or removed. assert ( await client.patch( f"/api/v1/trees/{tid}/members/{owner_mid}", json={"role": "editor"}, headers=owner ) ).status_code == 409 assert ( await client.delete(f"/api/v1/trees/{tid}/members/{owner_mid}", headers=owner) ).status_code == 409 # Owner removes the editor; the list shrinks and the editor loses access. assert (await client.delete(f"/api/v1/trees/{tid}/members/{mid}", headers=owner)).status_code == 204 assert len((await client.get(f"/api/v1/trees/{tid}/members", headers=owner)).json()) == 1 assert (await client.get(f"/api/v1/trees/{tid}/members", headers=ed)).status_code == 403