phase: 115_doc_draft_discard
All green — this was the final verification pass; everything from the four completed tasks was already in the working tree and verified. **Phase 115 — Doc drafts: Discard + DELETE route + title fix — verification report** - Verified all 4 task deliverables present: DELETE route (`app/api/doc_drafts.py`), Discard UI (`doc-edit.html` + `doc-edit.js` + `.discard-draft` CSS), title fix (`defaultDocTitle(wrap)` pairing + `saveAsDoc` call site), and all test pins (integration, frontend unit, E2E). No code changes needed. - **Completion criteria:** 1. ✅ Orphaned draft discardable from edit screen; row gone — `test_delete_removes_row_and_invalidates_token` (204 → GET 404), unknown-token 404, admin-gate 403 on all routes, E2E `test_discard_draft_from_edit_screen` all pass. 2. ✅ Title after retry redo = redone answer's own question — E2E `test_save_title_is_the_redo_question_after_retry` passes. 3. ✅ Push flow byte-identical — `git diff` shows only the new DELETE route + module docstring; all 7 existing push tests green. 4. ✅ `uv run pytest --cov=app` → **2457 passed**, app coverage **99%** (>90%); `uv run pytest tests/e2e/test_save_doc_session.py -v --no-cov` → **4 passed**; `uv run ruff check .` → clean; `uv run pyright` → 0 errors. 5. ⏳ Commit + phase-dir move left to the harness (per executor rules, no `git` run; all changes left in the working tree). - No defects found; no deviations. - Next pending phase: none in `todo/` other than this one (`115_doc_draft_discard` is the last).
This commit is contained in:
@@ -312,6 +312,63 @@ def test_put_rejects_blank_fields_and_leaves_row_unchanged(admin_client: TestCli
|
||||
assert admin_client.get(f"/api/doc-drafts/{created['token']}").json() == created
|
||||
|
||||
|
||||
# ---------- delete (by token — phase 115 Discard) ----------
|
||||
|
||||
|
||||
def test_delete_removes_row_and_invalidates_token(
|
||||
admin_client: TestClient, db
|
||||
) -> None:
|
||||
"""204 No Content; the row is gone from Postgres; after a discard
|
||||
the token is dead — GET/PUT/push all 404."""
|
||||
created = _create(admin_client)
|
||||
token = created["token"]
|
||||
|
||||
r = admin_client.delete(f"/api/doc-drafts/{token}")
|
||||
|
||||
assert r.status_code == 204
|
||||
assert r.content == b"" # 204: no body (the token is a one-way credential)
|
||||
row = db.execute(
|
||||
select(DocDraft).where(DocDraft.token == uuid.UUID(token))
|
||||
).scalars().first()
|
||||
assert row is None
|
||||
# Every sibling route now 404s with the same message as an unknown token.
|
||||
got = admin_client.get(f"/api/doc-drafts/{token}")
|
||||
assert got.status_code == 404
|
||||
assert got.json() == {"detail": "draft not found"}
|
||||
assert (
|
||||
admin_client.put(f"/api/doc-drafts/{token}", json={"body": "x"}).status_code
|
||||
== 404
|
||||
)
|
||||
assert admin_client.post(f"/api/doc-drafts/{token}/push").status_code == 404
|
||||
|
||||
|
||||
def test_delete_unknown_token_returns_404(admin_client: TestClient) -> None:
|
||||
r = admin_client.delete(f"/api/doc-drafts/{uuid.uuid4()}")
|
||||
assert r.status_code == 404
|
||||
assert r.json() == {"detail": "draft not found"}
|
||||
|
||||
|
||||
def test_delete_malformed_token_returns_422(admin_client: TestClient) -> None:
|
||||
assert admin_client.delete("/api/doc-drafts/not-a-uuid").status_code == 422
|
||||
|
||||
|
||||
def test_delete_works_on_pushed_draft_too(admin_client: TestClient, db) -> None:
|
||||
"""Nothing on the push side guards the row (no FK targets, no
|
||||
push-side state — the git push happens only on push): a draft that
|
||||
was already pushed is discarding-eligible; the row goes (the
|
||||
already-pushed file in the repo is out of scope — locked A1)."""
|
||||
created = _create(admin_client)
|
||||
token = uuid.UUID(created["token"])
|
||||
row = db.execute(select(DocDraft).where(DocDraft.token == token)).scalars().one()
|
||||
row.status = "pushed"
|
||||
row.branch = DOCS_BRANCH
|
||||
row.commit_sha = "a" * 40
|
||||
db.commit()
|
||||
|
||||
assert admin_client.delete(f"/api/doc-drafts/{token}").status_code == 204
|
||||
assert db.execute(select(DocDraft)).scalars().first() is None
|
||||
|
||||
|
||||
# ---------- auth: anonymous gets 403 on every route ----------
|
||||
|
||||
|
||||
@@ -324,6 +381,7 @@ def test_anonymous_gets_403_on_all_routes(admin_client: TestClient, db) -> None:
|
||||
assert r.json() == {"detail": "admin only"}
|
||||
assert anon.get(f"/api/doc-drafts/{created['token']}").status_code == 403
|
||||
assert anon.put(f"/api/doc-drafts/{created['token']}", json={"body": "nope"}).status_code == 403
|
||||
assert anon.delete(f"/api/doc-drafts/{created['token']}").status_code == 403
|
||||
|
||||
# The anonymous attempts changed nothing: exactly the admin's draft
|
||||
# exists, untouched.
|
||||
|
||||
Reference in New Issue
Block a user