Files

189 lines
8.4 KiB
Markdown

---
phase: "07.4"
plan: "01"
type: execute
wave: 0
depends_on: []
files_modified:
- backend/tests/test_auth_fgp.py
autonomous: true
requirements:
- FGP-CONCERN # tracked in .planning/codebase/CONCERNS.md §"No Token Fingerprint / Token Binding"
must_haves:
truths:
- "test_auth_fgp.py exists with exactly 4 test functions"
- "All 4 tests are decorated with @pytest.mark.xfail(strict=False, reason='not implemented yet')"
- "pytest -v reports all 4 as XFAIL — no failures, no errors"
- "No production source files are modified"
artifacts:
- path: "backend/tests/test_auth_fgp.py"
provides: "Wave 0 xfail stubs for FGP-01..FGP-04"
contains: "test_fgp_match_returns_200"
key_links:
- from: "backend/tests/test_auth_fgp.py"
to: "backend/tests/test_auth_deps.py"
via: "imports FakeRedis, copies make_test_app / auth_client / _create_user patterns"
pattern: "from tests.test_auth_api import FakeRedis"
---
<objective>
Create the Wave 0 test scaffold for Phase 7.4: a new test file `backend/tests/test_auth_fgp.py` containing 4 xfail stubs covering the four fingerprint behaviours (FGP-01..FGP-04). No production code is touched in this plan.
Purpose: Establishes the Nyquist test harness before any production code changes. Follows the xfail(strict=False) Wave 0 convention established in Phases 7.2 and 7.3.
Output: `backend/tests/test_auth_fgp.py` with 4 stubs; full test suite still passes with zero failures.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-CONTEXT.md
@.planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-RESEARCH.md
@.planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-PATTERNS.md
<interfaces>
<!-- Key patterns the executor needs. Extracted from codebase. -->
From backend/tests/test_auth_deps.py (full harness to copy):
Imports:
import uuid
import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from tests.test_auth_api import FakeRedis
make_test_app():
Creates minimal FastAPI app with /test/me route wired to get_current_user.
Sets app.state.redis = FakeRedis() so NBF check and fgp check can access request.app.state.redis.
auth_client fixture:
@pytest_asyncio.fixture
async def auth_client(db_session: AsyncSession):
app = make_test_app()
app.dependency_overrides[get_db] = lambda: db_session
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
yield c
app.dependency_overrides.clear()
_create_user helper (no decorator — plain async function):
Inserts minimal User row; returns User ORM object.
Fields: id=uuid.uuid4(), handle, email, password_hash, role, is_active=True
Test function pattern:
@pytest.mark.asyncio
async def test_name(auth_client, db_session):
from services.auth import create_access_token
user = await _create_user(db_session)
token = create_access_token(str(user.id), "user")
resp = await auth_client.get("/test/me", headers={"Authorization": f"Bearer {token}"})
assert resp.status_code == 200
conftest _patch_es256_test_keys is autouse=True at session scope — no explicit reference needed in this file.
From backend/tests/test_auth_es256.py (xfail stub pattern for Phase 7.3):
@pytest.mark.xfail(strict=False, reason="not implemented yet")
@pytest.mark.asyncio
async def test_stub_name(...):
pytest.xfail("not implemented yet")
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Create test_auth_fgp.py with 4 xfail stubs (FGP-01..FGP-04)</name>
<files>backend/tests/test_auth_fgp.py</files>
<read_first>
- backend/tests/test_auth_deps.py — copy the full harness (make_test_app, auth_client fixture, _create_user helper, imports block). This is the structural template; copy verbatim then modify test functions only.
- backend/tests/test_auth_es256.py — xfail stub decoration pattern used in Phase 7.3. Stubs use @pytest.mark.xfail(strict=False, reason="not implemented yet") and body is only pytest.xfail("not implemented yet").
- .planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-RESEARCH.md §"Test Case Structure" — exact test names and behaviour descriptions for all 4 tests.
</read_first>
<behavior>
- FGP-01 test_fgp_match_returns_200: issues token with user_agent="Mozilla/5.0" accept_lang="en", sends request with matching headers — expects 200
- FGP-02 test_fgp_mismatch_returns_401: issues token with user_agent="Mozilla/5.0" accept_lang="en", sends request with user_agent="different-agent" — expects 401 and detail="Token fingerprint mismatch"
- FGP-03 test_no_fgp_claim_allowed: crafts token manually without "fgp" key in payload — expects 200 (migration grace)
- FGP-04 test_missing_headers_empty_string_binding: issues token with no user_agent/accept_lang args (defaults to ""), sends request with no User-Agent/Accept-Language headers — expects 200
</behavior>
<action>
Create backend/tests/test_auth_fgp.py. Structure:
1. Module docstring explaining Phase 7.4 fgp test coverage (FGP-01..04).
2. Imports: copy the exact imports block from test_auth_deps.py. Add these additional imports needed for FGP-03 (manual token craft): `import base64`, `import time`, `import jwt as _jwt`, and `from config import settings`.
3. Copy make_test_app() verbatim from test_auth_deps.py (includes app.state.redis = FakeRedis()). Do NOT add a /test/admin route — not needed here.
4. Copy auth_client fixture verbatim from test_auth_deps.py.
5. Copy _create_user helper verbatim from test_auth_deps.py.
6. Write 4 test functions in this order:
- test_fgp_match_returns_200 (FGP-01)
- test_fgp_mismatch_returns_401 (FGP-02)
- test_no_fgp_claim_allowed (FGP-03)
- test_missing_headers_empty_string_binding (FGP-04)
Each test function:
- Decorated with @pytest.mark.xfail(strict=False, reason="not implemented yet") ABOVE @pytest.mark.asyncio
- Decorated with @pytest.mark.asyncio
- Accepts (auth_client, db_session) as parameters
- Body is a single line: pytest.xfail("not implemented yet")
Do NOT write any assertion logic, token construction, or request code inside the stubs. The stub body is ONLY the pytest.xfail() call. Implementation comes in Wave 1 (Plan 07.4-02).
7. No production files (services/auth.py, deps/auth.py, api/auth.py) may be modified in this task.
</action>
<verify>
<automated>cd /Users/nik/Documents/Progamming/document_scanner/backend && pytest tests/test_auth_fgp.py -v 2>&1 | tail -20</automated>
</verify>
<done>
- backend/tests/test_auth_fgp.py exists
- pytest tests/test_auth_fgp.py -v shows exactly 4 tests, all reported as XFAIL
- Zero FAILED, zero ERROR entries in the output
- pytest -v (full suite) still passes with zero failures
</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| test harness → test DB | xfail stubs create no DB state; no trust boundary crossed in Wave 0 |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-07.4-SC | Tampering | npm/pip/cargo installs | accept | No new packages installed in this plan; stdlib only |
</threat_model>
<verification>
After this plan completes:
- `pytest tests/test_auth_fgp.py -v` shows 4 XFAIL
- `pytest -v` (full suite) exits 0 with the same pass count as before this plan (411+) plus 4 new XFAIL
- No changes to backend/services/auth.py, backend/deps/auth.py, or backend/api/auth.py
</verification>
<success_criteria>
- `backend/tests/test_auth_fgp.py` exists with 4 test stubs (FGP-01..04)
- All 4 stubs use `@pytest.mark.xfail(strict=False, reason="not implemented yet")`
- Full test suite passes with zero failures; 4 new XFAIL added
- No production code modified
</success_criteria>
<output>
Create `.planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-01-SUMMARY.md` when done
</output>