---
phase: 07.2-security-jti-claim-redis-access-token-revocation-inserted
plan: "01"
subsystem: testing
tags:
  - security
  - jwt
  - testing
  - nyquist
  - wave-0

dependency_graph:
  requires: []
  provides:
    - "FakeRedis on test_auth_deps app.state.redis (Pitfall 4 guard)"
    - "FakeRedis on admin_client fixture app.state.redis (Pitfall 4 guard)"
    - "Wave 0 xfail stub: jti claim in create_access_token"
    - "Wave 0 xfail stubs: NBF check accept/reject/fail-open in get_current_user"
    - "Wave 0 xfail stubs: user_nbf write on change_password/enable_totp/disable_totp/admin-deactivation"
  affects:
    - "backend/tests/test_auth_deps.py"
    - "backend/tests/test_task2_auth_service.py"
    - "backend/tests/test_auth_api.py"
    - "backend/tests/test_admin_api.py"

tech_stack:
  added: []
  patterns:
    - "Wave 0 xfail(strict=False) scaffolding — stubs flip to xpass when Wave 1/2 implementation lands"
    - "FakeRedis imported from tests.test_auth_api (single canonical definition, never redefined)"
    - "app.state.redis = FakeRedis() set in fixture before yield, reset to None in teardown"

key_files:
  created: []
  modified:
    - backend/tests/test_auth_deps.py
    - backend/tests/test_task2_auth_service.py
    - backend/tests/test_auth_api.py
    - backend/tests/test_admin_api.py

decisions:
  - "Import FakeRedis from tests.test_auth_api — no redefinition in any file"
  - "FakeRedis instantiated inside make_test_app() so each test invocation gets fresh state"
  - "admin_client teardown resets app.state.redis = None to mirror authed_client convention"
  - "Wave 0 stubs use pytest.xfail() call inside body (not just marker) for the three NBF-write tests, consistent with minimal-body convention"
  - "test_activate_user_does_not_write_user_nbf added as negative-guard (mirrors RESEARCH.md anti-pattern)"

metrics:
  duration: "450s (7m)"
  completed_date: "2026-06-05T17:08:58Z"
  tasks_completed: 4
  tasks_total: 4
  files_changed: 4
---

# Phase 07.2 Plan 01: Wave 0 Test Scaffolding Summary

**One-liner:** FakeRedis mounted on two test fixtures + 9 xfail stubs covering every Phase 7.2 behavior (jti, NBF check × 3, NBF write × 4, negative-guard × 1).

## What Was Built

This plan is the Nyquist Wave 0 scaffolding for Phase 7.2. It creates no implementation code — only test stubs that define the expected behaviors before any code in `services/auth.py`, `deps/auth.py`, `api/auth.py`, or `api/admin.py` is modified.

### Task 1: FakeRedis on test_auth_deps + NBF-check stubs

- `test_auth_deps.py`: added `from tests.test_auth_api import FakeRedis` import
- `make_test_app()`: now sets `test_app.state.redis = FakeRedis()` before returning
- Added 3 xfail stubs with `@pytest.mark.xfail(strict=False)`:
  - `test_get_current_user_rejects_token_when_iat_before_user_nbf`
  - `test_get_current_user_allows_token_when_iat_after_user_nbf`
  - `test_get_current_user_failopen_on_redis_error` (uses inline `_BrokenRedis` class)

### Task 2: JTI presence stub in test_task2_auth_service.py

- Added `test_create_access_token_includes_jti_claim` decorated with `xfail(strict=False)`
- Calls `create_access_token` + `decode_access_token`, asserts `"jti"` key present and parseable as `uuid.UUID`

### Task 3: NBF-write stubs for change_password/enable_totp/disable_totp

- Added 3 xfail stubs to `test_auth_api.py`:
  - `test_change_password_writes_user_nbf_to_redis`
  - `test_enable_totp_writes_user_nbf_to_redis`
  - `test_disable_totp_writes_user_nbf_to_redis`
- Each test exercises the full auth flow then calls `pytest.xfail()` as the Wave 0 body

### Task 4: FakeRedis on admin_client + deactivation NBF-write stubs

- `test_admin_api.py`: added `from tests.test_auth_api import FakeRedis`
- `admin_client` fixture: sets `app.state.redis = FakeRedis()` before client yield, resets to `None` after
- Added 2 xfail stubs:
  - `test_deactivate_user_writes_user_nbf_to_redis` (positive case)
  - `test_activate_user_does_not_write_user_nbf` (negative-guard — activation must NOT write user_nbf)

## Verification

Baseline container test run: **73 passed** (across the 4 modified test files) — zero new failures.

Full project baseline: **373 passed** (Phase 7.1 complete state) — confirmed unaffected.

Acceptance criteria checks:
- `grep -c "FakeRedis" test_auth_deps.py` → 6 (import + assignment + stubs)
- `grep -c "test_app.state.redis" test_auth_deps.py` → 1
- 3 new NBF-check test functions in test_auth_deps.py
- 3 xfail markers in test_auth_deps.py
- `test_create_access_token_includes_jti_claim` in test_task2_auth_service.py
- 3 NBF-write test functions in test_auth_api.py
- `from tests.test_auth_api import FakeRedis` in test_admin_api.py
- `app.state.redis = FakeRedis()` in admin_client fixture
- 2 deactivation NBF stubs in test_admin_api.py

## Commits

| Task | Commit | Description |
|------|--------|-------------|
| 1 | 49c6333 | test(07.2-01): mount FakeRedis on test_auth_deps app + add NBF-check xfail stubs |
| 2 | c686d90 | test(07.2-01): add JTI claim xfail stub to test_task2_auth_service.py |
| 3 | 097cdca | test(07.2-01): add NBF-write xfail stubs for change_password/enable_totp/disable_totp |
| 4 | eb16472 | test(07.2-01): add FakeRedis to admin_client + NBF-write xfail stubs for deactivation |

## Deviations from Plan

None — plan executed exactly as written.

## Known Stubs

All 9 new test functions are intentional Wave 0 stubs. They are xfailed with `strict=False` and carry `assert False, "stub"` or `pytest.xfail()` in their bodies. Wave 1 (Plan 02) and Wave 2 (Plan 03) will promote them to passing assertions by replacing the stub bodies with real assertion logic. These are tracked in the plan and are the intended output of this wave.

## Patterns Established

- **FakeRedis import pattern**: always `from tests.test_auth_api import FakeRedis` — never redefine in another file
- **FakeRedis on test_auth_deps**: `test_app.state.redis = FakeRedis()` inside `make_test_app()` — fresh instance per test invocation
- **FakeRedis teardown**: `app.state.redis = None` after fixture yield — prevents state leak across test files
- **Wave 0 body convention**: `assert False, "stub — Wave N will fill in: <target assertion>"` or `pytest.xfail("stub")` — no real assertion logic

## Patterns to Avoid

- Do NOT redefine `FakeRedis` in `test_auth_deps.py`, `test_admin_api.py`, or any other file — import from `tests.test_auth_api` only
- Do NOT add `app.state.redis = None` reset inside the fixture loop — only in teardown after yield

## Threat Flags

None — this plan only modifies test files and introduces no new network endpoints, auth paths, or schema changes.

## Self-Check: PASSED

- [x] `backend/tests/test_auth_deps.py` modified and committed (49c6333)
- [x] `backend/tests/test_task2_auth_service.py` modified and committed (c686d90)
- [x] `backend/tests/test_auth_api.py` modified and committed (097cdca)
- [x] `backend/tests/test_admin_api.py` modified and committed (eb16472)
- [x] All 4 commits exist: `git log --oneline -4` confirms eb16472, 097cdca, c686d90, 49c6333
- [x] 73 baseline tests pass in container (zero regressions)
- [x] All 9 Wave 0 stubs present and correctly decorated with xfail(strict=False)
