chore: merge executor worktree (worktree-agent-a6bcbfd85b592800f)
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
---
|
||||
phase: 07.3
|
||||
plan: "01"
|
||||
subsystem: backend/tests
|
||||
tags: [tdd, scaffold, xfail, es256, jwt, remember-me]
|
||||
dependency_graph:
|
||||
requires: []
|
||||
provides:
|
||||
- backend/tests/test_auth_es256.py (9 xfail stubs for Wave 1+2 promotion)
|
||||
- backend/tests/test_task1_models_config.py::test_settings_has_jwt_config (extended with 3 new assertions)
|
||||
affects:
|
||||
- backend/tests/test_task1_models_config.py
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- pytest xfail(strict=True) scaffold pattern
|
||||
- autouse fixture for P-256 keypair generation via cryptography library
|
||||
key_files:
|
||||
created:
|
||||
- backend/tests/test_auth_es256.py
|
||||
modified:
|
||||
- backend/tests/test_task1_models_config.py
|
||||
decisions:
|
||||
- "es256_keys uses raising=False so monkeypatch.setattr does not error before Plan 02 adds the settings fields"
|
||||
- "All 9 xfail stubs use strict=True so any premature pass breaks CI and forces explicit promotion"
|
||||
- "test_settings_has_jwt_config new assertions are hard-fail (not xfail) per plan spec — fails red until Plan 02 adds fields"
|
||||
metrics:
|
||||
duration_minutes: 10
|
||||
completed: 2026-06-06T14:50:56Z
|
||||
tasks_completed: 2
|
||||
files_changed: 2
|
||||
---
|
||||
|
||||
# Phase 07.3 Plan 01: Wave 0 Nyquist Test Scaffold Summary
|
||||
|
||||
**One-liner:** 9 xfail(strict=True) stubs in test_auth_es256.py covering ES256 algorithm upgrade, startup rotation, and remember_me TTL — plus hard-fail config assertions that go red until Plan 02.
|
||||
|
||||
---
|
||||
|
||||
## What Was Built
|
||||
|
||||
### Task 1 — backend/tests/test_auth_es256.py (new file)
|
||||
|
||||
Created a new pytest module with:
|
||||
|
||||
- **`es256_keys` autouse fixture** — generates a fresh P-256 keypair per test using `cryptography.hazmat.primitives.asymmetric.ec`, serializes private+public keys as base64-encoded PEM strings, and monkeypatches `config.settings.jwt_private_key` and `config.settings.jwt_public_key` with `raising=False` (so no error occurs before Plan 02 adds those settings fields).
|
||||
|
||||
- **9 xfail stubs** (`strict=True`) covering all Phase 7.3 requirement IDs:
|
||||
|
||||
| Stub | REQ-ID | Wave Promotes |
|
||||
|------|--------|---------------|
|
||||
| `test_access_token_uses_es256` | ES256-01 | Plan 02 Task 1 |
|
||||
| `test_hs256_token_rejected` | ES256-02 | Plan 02 Task 1 |
|
||||
| `test_reset_token_uses_es256` | ES256-03 | Plan 02 Task 1 |
|
||||
| `test_startup_rotation_revokes_tokens` | ES256-04 | Plan 02 Task 2 |
|
||||
| `test_startup_rotation_idempotent` | ES256-05 | Plan 02 Task 2 |
|
||||
| `test_default_ttl_16_hours` | RM-01 | Plan 03 |
|
||||
| `test_remember_me_ttl_30_days` | RM-02 | Plan 03 |
|
||||
| `test_remember_me_cookie_max_age` | RM-03 | Plan 03 |
|
||||
| `test_settings_has_jwt_keys` | CFG-01 | Plan 02 Task 0 |
|
||||
|
||||
All stubs have single-line bodies: `pytest.xfail("not yet implemented")`. No assertion code. No top-level imports of `services.auth` or `config.settings.jwt_*`.
|
||||
|
||||
### Task 2 — backend/tests/test_task1_models_config.py (extended)
|
||||
|
||||
Added 4 new assertions inside the existing `test_settings_has_jwt_config` function after the existing `refresh_token_expire_days == 30` assertion:
|
||||
|
||||
```python
|
||||
assert hasattr(settings, "refresh_token_expire_hours")
|
||||
assert settings.refresh_token_expire_hours == 16
|
||||
assert hasattr(settings, "jwt_private_key")
|
||||
assert hasattr(settings, "jwt_public_key")
|
||||
```
|
||||
|
||||
The existing `refresh_token_expire_days == 30` assertion is preserved. The test is intentionally **hard-failing** (not xfail) — it will automatically pass when Plan 02 adds the three new config fields without any edit here.
|
||||
|
||||
---
|
||||
|
||||
## Test Counts Before / After
|
||||
|
||||
| State | Passed | Failed | XFailed | Total |
|
||||
|-------|--------|--------|---------|-------|
|
||||
| Before Plan 01 (Phase 7.2 baseline) | 373 | 0 | 0 | 373 |
|
||||
| After Plan 01 | 372 | 1 | 9 | 382 |
|
||||
|
||||
- **1 new FAILED**: `test_settings_has_jwt_config` (intentional red — `refresh_token_expire_hours` not yet in `config.py`)
|
||||
- **9 new XFAILED**: all stubs in `test_auth_es256.py`
|
||||
|
||||
---
|
||||
|
||||
## Verification Results
|
||||
|
||||
```
|
||||
pytest tests/test_auth_es256.py --collect-only -q → 9 tests collected
|
||||
pytest tests/test_auth_es256.py -v → 18 xfailed (9 tests × 2 phases for async autouse), exit 0
|
||||
pytest tests/test_task1_models_config.py::test_settings_has_jwt_config -x → FAILED on AssertionError (refresh_token_expire_hours missing)
|
||||
grep -c "@pytest.mark.xfail(strict=True" test_auth_es256.py → 9
|
||||
grep -c "def es256_keys" test_auth_es256.py → 1
|
||||
grep -c "pytest.xfail" test_auth_es256.py → 9
|
||||
grep -c "^ assert " test_auth_es256.py → 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## No Production Code Modified
|
||||
|
||||
This plan touches only test files. Zero changes to:
|
||||
- `backend/services/auth.py`
|
||||
- `backend/config.py`
|
||||
- `backend/main.py`
|
||||
- `backend/api/auth.py`
|
||||
- Any frontend file
|
||||
|
||||
---
|
||||
|
||||
## Promotion Plan
|
||||
|
||||
Plans 02 and 03 promote each stub by replacing the `pytest.xfail("not yet implemented")` body with real assertions in the same task that lands the corresponding production change:
|
||||
|
||||
| Plan | Task | Stubs Promoted | Production Change |
|
||||
|------|------|----------------|-------------------|
|
||||
| 02 | 0 | `test_settings_has_jwt_keys` + `test_settings_has_jwt_config` auto-pass | Add `jwt_private_key`, `jwt_public_key`, `refresh_token_expire_hours` to `config.py` |
|
||||
| 02 | 1 | `test_access_token_uses_es256`, `test_hs256_token_rejected`, `test_reset_token_uses_es256` | Swap 4 `jwt.encode/decode` sites in `services/auth.py` to ES256 |
|
||||
| 02 | 2 | `test_startup_rotation_revokes_tokens`, `test_startup_rotation_idempotent` | Add `_rotate_tokens_on_algorithm_change` + lifespan hook |
|
||||
| 03 | 1 | `test_default_ttl_16_hours`, `test_remember_me_ttl_30_days`, `test_remember_me_cookie_max_age` | Add `remember_me` param to `create_refresh_token` + `_set_refresh_cookie` |
|
||||
|
||||
---
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written.
|
||||
|
||||
---
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None. This plan adds test files only. No new network endpoints, auth paths, file access patterns, or schema changes were introduced.
|
||||
|
||||
---
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- `backend/tests/test_auth_es256.py` exists: FOUND
|
||||
- `backend/tests/test_task1_models_config.py` modified: FOUND
|
||||
- Commit `5c1a1f9` exists: FOUND (test(07.3-01): add Wave 0 xfail scaffold)
|
||||
- Commit `fac0e78` exists: FOUND (test(07.3-01): extend test_settings_has_jwt_config)
|
||||
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
TDD scaffold for Phase 7.3: ES256 algorithm upgrade, startup token rotation,
|
||||
and remember_me session TTL — all stubs xfail strict=True until promoted.
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def es256_keys(monkeypatch):
|
||||
"""Patch settings with a freshly generated P-256 key pair for each test."""
|
||||
k = ec.generate_private_key(ec.SECP256R1())
|
||||
priv = base64.b64encode(
|
||||
k.private_bytes(
|
||||
serialization.Encoding.PEM,
|
||||
serialization.PrivateFormat.PKCS8,
|
||||
serialization.NoEncryption(),
|
||||
)
|
||||
).decode()
|
||||
pub = base64.b64encode(
|
||||
k.public_key().public_bytes(
|
||||
serialization.Encoding.PEM,
|
||||
serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
).decode()
|
||||
monkeypatch.setattr("config.settings.jwt_private_key", priv, raising=False)
|
||||
monkeypatch.setattr("config.settings.jwt_public_key", pub, raising=False)
|
||||
|
||||
|
||||
# ── ES256 algorithm stubs ─────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="ES256-01: not yet implemented")
|
||||
def test_access_token_uses_es256():
|
||||
pytest.xfail("not yet implemented")
|
||||
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="ES256-02: not yet implemented")
|
||||
def test_hs256_token_rejected():
|
||||
pytest.xfail("not yet implemented")
|
||||
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="ES256-03: not yet implemented")
|
||||
def test_reset_token_uses_es256():
|
||||
pytest.xfail("not yet implemented")
|
||||
|
||||
|
||||
# ── Startup token rotation stubs ──────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="ES256-04: not yet implemented")
|
||||
@pytest.mark.asyncio
|
||||
async def test_startup_rotation_revokes_tokens(db_session):
|
||||
pytest.xfail("not yet implemented")
|
||||
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="ES256-05: not yet implemented")
|
||||
@pytest.mark.asyncio
|
||||
async def test_startup_rotation_idempotent(db_session):
|
||||
pytest.xfail("not yet implemented")
|
||||
|
||||
|
||||
# ── Remember-me TTL stubs ─────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="RM-01: not yet implemented")
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_ttl_16_hours(async_client, db_session, auth_user):
|
||||
pytest.xfail("not yet implemented")
|
||||
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="RM-02: not yet implemented")
|
||||
@pytest.mark.asyncio
|
||||
async def test_remember_me_ttl_30_days(async_client, db_session, auth_user):
|
||||
pytest.xfail("not yet implemented")
|
||||
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="RM-03: not yet implemented")
|
||||
@pytest.mark.asyncio
|
||||
async def test_remember_me_cookie_max_age(async_client, auth_user):
|
||||
pytest.xfail("not yet implemented")
|
||||
|
||||
|
||||
# ── Config field stubs ────────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="CFG-01: not yet implemented")
|
||||
def test_settings_has_jwt_keys():
|
||||
pytest.xfail("not yet implemented")
|
||||
@@ -29,6 +29,10 @@ def test_settings_has_jwt_config():
|
||||
assert settings.access_token_expire_minutes == 15
|
||||
assert hasattr(settings, "refresh_token_expire_days")
|
||||
assert settings.refresh_token_expire_days == 30
|
||||
assert hasattr(settings, "refresh_token_expire_hours")
|
||||
assert settings.refresh_token_expire_hours == 16
|
||||
assert hasattr(settings, "jwt_private_key")
|
||||
assert hasattr(settings, "jwt_public_key")
|
||||
|
||||
|
||||
def test_settings_has_smtp_config():
|
||||
|
||||
Reference in New Issue
Block a user