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