76b7f453c1
Events and people are now editable, not write-once: PATCH /events/{id} (type, structured date, place, notes) and PATCH /persons/{id} (vitals, privacy, and the primary name's given/surname). CLAUDE.md gains rule #8: every stored object must support full CRUD in API and UI — historical research is constant correction. Tests cover both updates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
"""End-to-end coverage of the core data model through the API: tenancy, the
|
|
privacy seam, and real session auth."""
|
|
|
|
from tests.conftest import auth, register
|
|
|
|
|
|
async def test_tree_and_person_flow(client):
|
|
token = await register(client, "keeper@example.com")
|
|
|
|
resp = await client.post(
|
|
"/api/v1/trees",
|
|
json={"name": "Smith Family", "visibility": "private"},
|
|
headers=auth(token),
|
|
)
|
|
assert resp.status_code == 201, resp.text
|
|
tree = resp.json()
|
|
assert tree["visibility"] == "private"
|
|
tree_id = tree["id"]
|
|
|
|
resp = await client.get("/api/v1/trees", headers=auth(token))
|
|
assert resp.status_code == 200
|
|
assert len(resp.json()) == 1
|
|
|
|
resp = await client.post(
|
|
f"/api/v1/trees/{tree_id}/persons",
|
|
json={"given": "John", "surname": "Smith"},
|
|
headers=auth(token),
|
|
)
|
|
assert resp.status_code == 201, resp.text
|
|
person = resp.json()
|
|
assert person["primary_name"] == "John Smith"
|
|
assert person["tree_id"] == tree_id
|
|
|
|
resp = await client.get(f"/api/v1/trees/{tree_id}/persons", headers=auth(token))
|
|
assert resp.status_code == 200
|
|
assert len(resp.json()) == 1
|
|
|
|
|
|
async def test_private_tree_isolated_from_other_users(client):
|
|
owner = await register(client, "owner@example.com")
|
|
other = await register(client, "stranger@example.com")
|
|
|
|
resp = await client.post(
|
|
"/api/v1/trees", json={"name": "Private", "visibility": "private"}, headers=auth(owner)
|
|
)
|
|
tree_id = resp.json()["id"]
|
|
|
|
resp = await client.get(f"/api/v1/trees/{tree_id}", headers=auth(other))
|
|
assert resp.status_code == 403
|
|
resp = await client.get(f"/api/v1/trees/{tree_id}/persons", headers=auth(other))
|
|
assert resp.status_code == 403
|
|
|
|
|
|
async def test_public_tree_viewable_but_not_editable_by_non_member(client):
|
|
owner = await register(client, "owner2@example.com")
|
|
viewer = await register(client, "viewer2@example.com")
|
|
|
|
resp = await client.post(
|
|
"/api/v1/trees", json={"name": "Public", "visibility": "public"}, headers=auth(owner)
|
|
)
|
|
tree_id = resp.json()["id"]
|
|
|
|
resp = await client.get(f"/api/v1/trees/{tree_id}", headers=auth(viewer))
|
|
assert resp.status_code == 200
|
|
resp = await client.post(
|
|
f"/api/v1/trees/{tree_id}/persons", json={"given": "Nope"}, headers=auth(viewer)
|
|
)
|
|
assert resp.status_code == 403
|
|
|
|
|
|
async def test_person_update(client):
|
|
token = await register(client, "edit@example.com")
|
|
h = auth(token)
|
|
tid = (await client.post("/api/v1/trees", json={"name": "T"}, headers=h)).json()["id"]
|
|
pid = (
|
|
await client.post(
|
|
f"/api/v1/trees/{tid}/persons", json={"given": "Jon", "surname": "Smith"}, headers=h
|
|
)
|
|
).json()["id"]
|
|
resp = await client.patch(
|
|
f"/api/v1/trees/{tid}/persons/{pid}",
|
|
json={"given": "John", "gender": "male"},
|
|
headers=auth(token),
|
|
)
|
|
assert resp.status_code == 200, resp.text
|
|
assert resp.json()["primary_name"] == "John Smith"
|
|
assert resp.json()["gender"] == "male"
|
|
|
|
|
|
async def test_auth_required_without_token(client):
|
|
resp = await client.get("/api/v1/trees")
|
|
assert resp.status_code == 401
|