feat(06.2-02): backend — ShareCreate.permission field + PATCH /{share_id} endpoint

- Add permission field (default "view") with field_validator to ShareCreate
- Add SharePermissionPatch model with same validator
- Wire body.permission into grant_share() Share constructor
- Add PATCH /{share_id} endpoint with IDOR protection (T-06.2-02-01)
- Promote 3 xfail stubs to real tests (create_with_permission, patch_permission, patch_idor)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-05-31 15:04:53 +02:00
parent 7e62868fea
commit ea231853e9
2 changed files with 143 additions and 5 deletions
+92 -3
View File
@@ -352,14 +352,103 @@ async def test_share_indicator_in_owner_list(async_client, auth_user, second_aut
async def test_share_create_with_permission(async_client, auth_user, second_auth_user, db_session):
"""POST /api/shares respects permission field from request body (SHARE-03, D-08, D-10)"""
pytest.xfail("Phase 6.2 — not implemented yet")
doc_id = await _make_doc(db_session, auth_user)
# POST with explicit permission="edit" — must be stored and returned
resp = await async_client.post(
"/api/shares",
json={
"document_id": doc_id,
"recipient_handle": second_auth_user["user"].handle,
"permission": "edit",
},
headers=auth_user["headers"],
)
assert resp.status_code == 201, resp.text
body = resp.json()
assert body["permission"] == "edit", (
f"Expected permission='edit' in POST /api/shares response, got {body.get('permission')!r}"
)
# POST without permission field defaults to "view"
# Use a third document to avoid duplicate share constraint
doc_id2 = await _make_doc(db_session, second_auth_user)
resp2 = await async_client.post(
"/api/shares",
json={
"document_id": doc_id2,
"recipient_handle": auth_user["user"].handle,
},
headers=second_auth_user["headers"],
)
assert resp2.status_code == 201, resp2.text
body2 = resp2.json()
assert body2["permission"] == "view", (
f"Expected default permission='view', got {body2.get('permission')!r}"
)
async def test_share_patch_permission(async_client, auth_user, second_auth_user, db_session):
"""PATCH /api/shares/{id} changes permission to edit (SHARE-03, D-09)"""
pytest.xfail("Phase 6.2 — not implemented yet")
doc_id = await _make_doc(db_session, auth_user)
# Create a share with default permission (view)
share_resp = await async_client.post(
"/api/shares",
json={
"document_id": doc_id,
"recipient_handle": second_auth_user["user"].handle,
},
headers=auth_user["headers"],
)
assert share_resp.status_code == 201, share_resp.text
share_id = share_resp.json()["id"]
# PATCH to "edit"
patch_resp = await async_client.patch(
f"/api/shares/{share_id}",
json={"permission": "edit"},
headers=auth_user["headers"],
)
assert patch_resp.status_code == 200, patch_resp.text
assert patch_resp.json()["permission"] == "edit", (
f"Expected permission='edit' after PATCH, got {patch_resp.json().get('permission')!r}"
)
# PATCH back to "view"
patch_resp2 = await async_client.patch(
f"/api/shares/{share_id}",
json={"permission": "view"},
headers=auth_user["headers"],
)
assert patch_resp2.status_code == 200, patch_resp2.text
assert patch_resp2.json()["permission"] == "view", (
f"Expected permission='view' after second PATCH, got {patch_resp2.json().get('permission')!r}"
)
async def test_share_patch_idor(async_client, auth_user, second_auth_user, db_session):
"""PATCH /api/shares/{id} by non-owner returns 404 — IDOR protection (SHARE-03, D-09, T-IDOR)"""
pytest.xfail("Phase 6.2 — not implemented yet")
doc_id = await _make_doc(db_session, auth_user)
# Create share owned by auth_user
share_resp = await async_client.post(
"/api/shares",
json={
"document_id": doc_id,
"recipient_handle": second_auth_user["user"].handle,
},
headers=auth_user["headers"],
)
assert share_resp.status_code == 201, share_resp.text
share_id = share_resp.json()["id"]
# second_auth_user attempts to PATCH a share they do not own — must be 404 (not 403)
patch_resp = await async_client.patch(
f"/api/shares/{share_id}",
json={"permission": "edit"},
headers=second_auth_user["headers"],
)
assert patch_resp.status_code == 404, (
f"Non-owner should get 404 on PATCH (IDOR protection), got {patch_resp.status_code}"
)