# Test — Auth & Token Handling

**Task**: AZ-457_test_auth_token_handling
**Name**: Auth & token-handling blackbox suite
**Description**: Implement every blackbox test that exercises the in-memory bearer, the refresh cookie, the 401→refresh→retry path, and the redirect-to-/login flow. Spans `fast` (MSW) and `e2e` (real `admin/`) profiles.
**Complexity**: 5 points
**Dependencies**: AZ-456_test_infrastructure
**Component**: 01_api-transport + 02_auth (Blackbox Tests)
**Tracker**: AZ-457
**Epic**: AZ-455

## Problem

The SPA's auth surface (`<AuthContext>` + `src/api/client.ts` + `<ProtectedRoute>`) is the gate every other test depends on. It mixes a memory-only bearer (AC-02 / O2), a HttpOnly refresh cookie (AC-03), a 401-retry loop (AC-23), and a redirect on refresh failure (C06 from autodev Step 4). Tests must lock down the wire contract AND the storage contract without leaking into production.

## Outcome

- 11 test scenarios pass in their declared profile, all referencing `results_report.md` rows for expected observables.
- The fast suite uses MSW to drive the `/api/admin/auth/refresh` path; e2e uses the real `admin/` service.
- No test stubs `src/api/client.ts`, `<AuthContext>`, or `<ProtectedRoute>` internals — every assertion is observable at the DOM, network, or browser-storage surface.

## Scope

### Included

| Scenario | Profile | Source file | results_report row |
|----------|---------|-------------|--------------------|
| FT-P-01 — bootstrap refresh sends `credentials:'include'` | fast | blackbox-tests.md | 02 |
| FT-P-02 — 401 → refresh → retry sequence | fast + e2e | blackbox-tests.md | 03, 12 |
| FT-P-03 — refresh transparency, no `<ProtectedRoute>` unmount | fast | blackbox-tests.md | 11 |
| FT-N-04 — unauthenticated `/admin` → redirect to `/login` | fast | blackbox-tests.md | row(s) per blackbox-tests.md FT-N-04 |
| NFT-SEC-01 — bearer never in localStorage/sessionStorage | fast | security-tests.md | 01 |
| NFT-SEC-02 — refresh cookie not in `document.cookie` | fast | security-tests.md | 04 |
| NFT-SEC-03 — refresh cookie has `Secure; HttpOnly; SameSite=Strict` | e2e | security-tests.md | 05 |
| NFT-SEC-04 — `credentials:'include'` on every authed fetch | fast | security-tests.md | 06 |
| NFT-PERF-02 — exactly one refresh round trip per cycle | fast | performance-tests.md | 12 |
| NFT-RES-01 — 401→refresh→retry is transparent end-to-end | fast | resilience-tests.md | 03, 11 |
| NFT-RES-08 — refresh cookie expired → redirect to `/login` | fast | resilience-tests.md | row(s) per NFT-RES-08 |

### Excluded

- SSE bearer rotation (covered in 03_test_sse_lifecycle).
- `/settings` / `/admin` RBAC beyond the unauthenticated-redirect case (covered in 12_test_protected_route_rbac).
- ConfirmDialog and destructive-action gating (covered in 11_test_destructive_ux).

## Acceptance Criteria

**AC-1: All 11 scenarios implemented**
Given the test infrastructure from AZ-456 is in place,
When `bun run test:fast` and `bun run test:e2e` execute,
Then every scenario above is present, runs in its declared profile, and references its `results_report.md` row in test comments.

**AC-2: Black-box discipline**
No test imports anything from `src/api/client.ts` (other than the public `setToken` / `getToken` / `setNavigateToLogin` accessors per AC-23 / autodev Step 4 testability changes); no test imports `<AuthContext>` internals; no test asserts on React state directly.

**AC-3: Storage assertions are exhaustive**
NFT-SEC-01 / NFT-SEC-02 assert that **for the duration of the test** the bearer never appears in `localStorage`, `sessionStorage`, or `document.cookie` — not just at one snapshot.

**AC-4: Redirect assertion uses the accessor**
FT-N-04 and NFT-RES-08 install a `setNavigateToLogin(spy)` and assert `spy` was called exactly once with no arguments; they do NOT globally stub `window.location`.

## System Under Test Boundary

- The system under test is the assembled SPA: React tree + `src/api/client.ts` + `<AuthContext>` + `<ProtectedRoute>` + Router.
- Allowed stubs: the suite's `admin/` `auth/refresh` and `auth/login` endpoints — stubbed via MSW in `fast`, real service in `e2e`.
- Disallowed: stubbing `src/api/client.ts`, `<AuthContext>`, `<ProtectedRoute>`, or any other internal SPA module. If `<AuthContext>` is not behaving as expected, the test FAILS — it does not bypass it.
- Expected observables compared against `_docs/00_problem/input_data/expected_results/results_report.md` rows 02, 03, 04, 05, 06, 11, 12, and the rows for FT-N-04 / NFT-RES-08 as listed in `traceability-matrix.md`.

## Constraints

- One test file per top-level concern; co-locate next to source (e.g., `src/api/client.test.ts`, `src/auth/AuthContext.test.tsx`, `src/auth/ProtectedRoute.test.tsx`).
- MSW handlers live in `tests/msw/handlers/admin.ts`; per-test overrides via `server.use(...)` only.
- E2E auth uses the `op_alice` seed user (test-data.md).

## Risks & Mitigation

**Risk 1 — Refresh-cookie HttpOnly invisibility**
- *Risk*: AC-03 requires `HttpOnly` — by spec, the cookie is invisible to JS, so the fast profile cannot directly assert presence.
- *Mitigation*: in `fast`, MSW asserts the response `Set-Cookie` header carries the three flags (the server contract). In `e2e`, the test uses Playwright's `context.cookies()` (which sees HttpOnly cookies) to verify presence + flags. NFT-SEC-03 runs e2e-only.
