phase: 115_doc_draft_discard
Build and Push Containers / build-and-push-app (push) Successful in 2m12s
Build and Push Containers / build-and-push-db (push) Successful in 14s

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:
2026-09-15 05:35:05 -04:00
parent 3846f26a58
commit 990c8adf13
29 changed files with 1384 additions and 30 deletions
+58
View File
@@ -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.