docs(08-01): complete Wave 0 xfail stubs plan — 3 xpassed, CR-01/02/03 contracts locked
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
f750d30224
commit
c636ac956f
@@ -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
|
||||||
Reference in New Issue
Block a user