From c636ac956f6a04d70e7fdb63aa208bf256ac922f Mon Sep 17 00:00:00 2001 From: curo1305 Date: Mon, 8 Jun 2026 16:23:13 +0200 Subject: [PATCH] =?UTF-8?q?docs(08-01):=20complete=20Wave=200=20xfail=20st?= =?UTF-8?q?ubs=20plan=20=E2=80=94=203=20xpassed,=20CR-01/02/03=20contracts?= =?UTF-8?q?=20locked?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../08-01-SUMMARY.md | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 .planning/phases/08-stack-upgrade-backend-decomposition/08-01-SUMMARY.md diff --git a/.planning/phases/08-stack-upgrade-backend-decomposition/08-01-SUMMARY.md b/.planning/phases/08-stack-upgrade-backend-decomposition/08-01-SUMMARY.md new file mode 100644 index 0000000..d1be1db --- /dev/null +++ b/.planning/phases/08-stack-upgrade-backend-decomposition/08-01-SUMMARY.md @@ -0,0 +1,129 @@ +--- +phase: 08-stack-upgrade-backend-decomposition +plan: "01" +subsystem: backend-tests +tags: [tests, session-revocation, xfail, wave-0, cr-01, cr-02, cr-03] +dependency_graph: + requires: [] + provides: + - "behavioral contract for CR-01: change_password revokes other sessions" + - "behavioral contract for CR-02: enable_totp revokes other sessions" + - "behavioral contract for CR-03: disable_totp revokes other sessions" + affects: + - "backend/tests/test_auth.py — new file" +tech_stack: + added: [] + patterns: + - "xfail(strict=False) Wave 0 scaffold — tests pass now (xpassed), marker removed in 08-03" + - "FakeRedis in-memory store for all auth/TOTP tests (established pattern)" + - "cookies= kwarg for explicit refresh token injection bypassing path restriction" + - "patch.dict(sys.modules) to prevent Celery broker connection on token replay" +key_files: + created: + - path: "backend/tests/test_auth.py" + description: "Three xfail tests for CR-01/CR-02/CR-03 session revocation contracts" + modified: [] +decisions: + - "Used fixed User-Agent on revoke_client fixture to match fgp claim in access tokens" + - "Used DB-direct TOTP enable for CR-03 test to avoid extraneous setup session token" + - "Patched services.auth.verify_totp for CR-03 TOTP logins to bypass 90s replay prevention" + - "Patched tasks.email_tasks via patch.dict(sys.modules) to avoid real Celery connection" +metrics: + duration: "10m 8s" + completed: "2026-06-08" + tasks_completed: 1 + tasks_total: 1 + files_created: 1 + files_modified: 0 +--- + +# Phase 8 Plan 01: Wave 0 xfail Stubs for CR-01/CR-02/CR-03 Summary + +**One-liner:** Three xfail(strict=False) tests locking session-revocation contracts for change_password, enable_totp, and disable_totp — all xpassed since backend is already complete. + +## What Was Built + +Created `backend/tests/test_auth.py` with three Wave 0 test stubs: + +| Test | Requirement | Status | +|------|------------|--------| +| `test_change_password_revokes_other_sessions` | CR-01 | xpassed | +| `test_enable_totp_revokes_other_sessions` | CR-02 | xpassed | +| `test_disable_totp_revokes_other_sessions` | CR-03 | xpassed | + +All three tests pass when run with `--runxfail` (backend already implements the behavior per RESEARCH.md §Wave 1). They show `XPASS` in normal mode since `strict=False`. + +## Test Infrastructure + +**Fixtures used:** +- `revoke_client` (new, defined in test_auth.py): AsyncClient with FakeRedis, fixed `User-Agent: docuvault-test/1.0`, DB override +- `db_session` (from conftest.py): in-memory SQLite session + +**Helper functions:** +- `_register_user(client, handle, email)` — register + assert 201 +- `_login_session(client, email)` — login + return (access_token, refresh_cookie) +- `_try_refresh(client, refresh_token)` — POST /api/auth/refresh + return status code + +**xfail decorator reason string:** `"Wave 0 stub — promoted to passing in 08-03"` + +## Pytest Collection Count + +- Pre-change baseline: 0 tests in `test_auth.py` (file did not exist) +- Post-change: 3 tests collected + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] FakeRedis not set on app.state.redis** +- **Found during:** Task 1 implementation +- **Issue:** The plan's existing `authed_client` fixture pattern required FakeRedis injection on `app.state.redis`. Without it, endpoints that call `request.app.state.redis.set(...)` (change_password, enable_totp, disable_totp) would fail. +- **Fix:** Created dedicated `revoke_client` fixture with FakeRedis injection, matching the pattern from `test_auth_api.py`. +- **Files modified:** `backend/tests/test_auth.py` + +**2. [Rule 1 - Bug] Token fingerprint mismatch on API calls** +- **Found during:** Task 1 — first test run +- **Issue:** The access token's `fgp` claim is bound to the User-Agent at login time. The plan suggested using distinct User-Agents for session A and B to ensure separate DB rows, but this caused fingerprint mismatch when using token_a in subsequent API calls with a different User-Agent. +- **Fix:** Used a single fixed User-Agent (`"docuvault-test/1.0"`) for the `revoke_client` fixture. Two sequential logins always create two separate RefreshToken rows regardless of User-Agent. +- **Files modified:** `backend/tests/test_auth.py` + +**3. [Rule 1 - Bug] TOTP replay prevention blocks second session login in CR-03** +- **Found during:** Task 1 — second test run +- **Issue:** The FakeRedis stores TOTP used-code keys with a 90s TTL. When `_login_with_totp()` was called twice in the same 30-second TOTP window, `pyotp.TOTP(secret).now()` returned the same code which was already marked used in FakeRedis. +- **Fix:** Patched `services.auth.verify_totp` to return `True` for the TOTP login calls in CR-03. The test focuses on session revocation, not TOTP validation. +- **Files modified:** `backend/tests/test_auth.py` + +**4. [Rule 1 - Bug] Celery broker connection attempt on revoked token** +- **Found during:** Task 1 — third test run +- **Issue:** When `_try_refresh` is called with session B's revoked token, `rotate_refresh_token` triggers the family-revocation path which calls `send_security_alert_email.delay(...)`. This attempted a real Redis/Celery broker connection (not available in unit tests). +- **Fix:** Patched `tasks.email_tasks` via `patch.dict("sys.modules", {...})` inside `_try_refresh`, matching the pattern from `test_task2_auth_service.py`. +- **Files modified:** `backend/tests/test_auth.py` + +## Verification + +``` +3 xpassed, 10 warnings in 2.02s +``` + +Full backend suite before this plan (pre-existing): `1 failed (test_extract_docx — ModuleNotFoundError: docx not installed locally), 402 passed` + +Full backend suite after this plan: same baseline + 3 xpassed new tests added. + +The pre-existing `test_extractor.py::test_extract_docx` failure is a `ModuleNotFoundError: No module named 'docx'` — the `python-docx` package is only installed inside Docker, not in the local Python environment. This is out-of-scope and was pre-existing before Plan 08-01. + +## Threat Flags + +None — this plan only adds tests, no new network endpoints, auth paths, file access patterns, or schema changes. + +## Known Stubs + +None — the test bodies contain full assertion logic, not `pass` placeholders. + +## Self-Check: PASSED + +- [x] `backend/tests/test_auth.py` created and contains 3 test functions +- [x] All three function names match the exact names specified in the plan +- [x] `grep -c "pytest.mark.xfail" backend/tests/test_auth.py` = 4 (3 decorators + 1 in docstring, baseline was 0) +- [x] Commit `f750d30` exists: `git log --oneline | grep f750d30` +- [x] Tests show XPASS status (strict=False xfail) +- [x] No new failures in the full suite