--- 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