# Wrap login redirect in a thin accessor

**Task**: AZ-453_refactor_navigate_to_login
**Name**: `navigateToLoginImpl()` accessor for the failed-refresh redirect
**Description**: Replace the direct `window.location.href = '/login'` write in `request()` with a thin overridable function so tests can verify the redirect call without globally stubbing `window.location`.
**Complexity**: 2 points
**Dependencies**: None
**Component**: `src/api/client.ts`
**Tracker**: AZ-453
**Epic**: AZ-447

## Problem

`src/api/client.ts:34` performs `window.location.href = '/login'` directly after a failed refresh. The DOM API is global; jsdom can intercept it, but a test cannot distinguish "library code redirected" from "user code redirected" without overriding `window.location` globally — a heavy and side-effecty pattern.

## Outcome

- A module-level `navigateToLoginImpl: () => void` is introduced, defaulting to the existing redirect.
- `setNavigateToLogin(fn)` is exported for test override.
- `request()` calls `navigateToLoginImpl()` instead of touching `window.location` directly.
- Production behavior is identical (default impl still navigates to `/login`).

## Scope

### Included
- `navigateToLoginImpl` and `setNavigateToLogin` additions in `src/api/client.ts`.
- One call-site change inside `request()`.

### Excluded
- Any change to the 401 / refresh flow itself.
- Any change to the redirect target string (`/login`).

## Acceptance Criteria

**AC-1: Production default unchanged**
Given the default implementation,
When a 401 returns and `refreshToken()` resolves to `false`,
Then `window.location.href === '/login'` after the failed-refresh branch executes.

**AC-2: Override is honored**
Given a test calls `setNavigateToLogin(spy)`,
When a 401 returns and refresh fails,
Then `spy` is called exactly once and `window.location.href` is NOT mutated by `client.ts`.

**AC-3: API surface preserved**
Given the rest of the module after the change,
When the exports are enumerated,
Then `setToken`, `getToken`, `api`, `setNavigateToLogin` are present and no existing export is renamed or removed.

## Non-Functional Requirements

**Compatibility**
- Tests that already override `window.location` continue to work (the default impl still goes through it).

## Blackbox Tests

| AC Ref | Initial Data / Conditions | What to Test | Expected Behavior | NFR References |
|--------|--------------------------|--------------|-------------------|----------------|
| AC-1   | No override; 401 + failed refresh | `window.location.href` after the call | `/login` | Compat |
| AC-2   | `setNavigateToLogin(spy)` then 401 + failed refresh | Spy call count + `window.location.href` | Spy = 1; href untouched by `client.ts` | Compat |
| AC-3   | Module exports diff | Existing exports | Present | API stability |

## Constraints

- Allowed-change category: "Wrap global singletons in thin accessors that tests can override" (existing-code flow Step 4 allowed list).

## Risks & Mitigation

**Risk 1: Forgetting to reset the override between tests**
- *Risk*: If a test sets the override and doesn't reset it, later tests see the wrong impl.
- *Mitigation*: Document in `_docs/02_document/tests/test-data.md` that helpers must reset accessors in their teardown. Optional follow-up: a `resetNavigateToLogin()` helper — but adding it expands scope; defer unless decompose requires it.
