test(worker): Auth (context/storage/login) — fill gaps + lifecycle test

Drive the worker Auth domain to 100% coverage:
- AuthContext.tsx: 91.3/80 -> 100/100 (signIn body, mount-restore branches, useAuth guard)
- Login.tsx: 78/50 -> 100/100 (handleSubmit success/error/pending, submit disable)
- auth-storage.ts: already 100 (no change)

New Login.test.tsx adds the domain lifecycle test: from the unauthenticated app,
a successful login stores the token and flips the context to the authenticated
tabbed shell. Extended AuthContext.test.tsx covers signIn delegation, mount
restore (token present/absent), and the out-of-provider throw.

No bugs found; code was already correct. Note: the design checklist's
'clears on 401' worker behavior is not implemented (no global 401 interceptor)
and is left as-is (missing feature, not a defect).
This commit is contained in:
Bas van Rossem
2026-07-23 13:50:33 +02:00
parent 9c99d56f99
commit 36d4754722
3 changed files with 223 additions and 1 deletions

View File

@@ -255,3 +255,31 @@ Guaranteed behaviors:
and the bearer authorizes `/api/me` → admin deactivates → the session rows are deleted so the
pre-issued token no longer authorizes `/api/me` (401) **and** a fresh sign-in is blocked → admin
reactivates → the ban is cleared and sign-in works again.
## Auth (context/storage/login) — worker client
Src covered (worker `apps/worker`): `src/auth/AuthContext.tsx`, `src/lib/auth-storage.ts`, and
`src/screens/Login.tsx` — all at 100% stmts/branch/funcs/lines. No `c8 ignore`s needed.
Guaranteed behaviors:
- `auth-storage`: `setToken` persists the bearer, `getToken` reads it back, `clearToken` removes it
(`getToken` returns `null` when nothing is stored) — all via `localStorage`.
- `AuthContext` starts **unauthenticated** when no token is stored and **restores** the
authenticated state on mount when a token is already present (`getToken() !== null`).
- `AuthContext.signIn` delegates to the api `signIn(email, password)` and, on success, flips the
context to authenticated (`isAuthed` → true).
- `AuthContext.signOut` resets the router path to `/` (so the next authed mount lands on Stopwatch,
not Account), clears the stored token, and flips the context to unauthenticated.
- `useAuth` throws `"useAuth must be used within an AuthProvider"` when called outside the provider.
- `Login` renders the Dutch form (E-mailadres / Wachtwoord / Inloggen) and submits the typed
credentials to `POST /api/auth/sign-in/email`, storing the returned bearer token.
- `Login` shows the `"Inloggen mislukt"` error (and stores no token) when sign-in fails, and
disables the submit button while the request is pending, re-enabling it once it settles.
- Lifecycle (`src/screens/Login.test.tsx`): from the unauthenticated app, a successful login stores
the token and flips the context so the tabbed authenticated shell (Stopwatch + TabBar) renders.
> Note: the design checklist mentions "clears on 401" for the worker auth context — this is **not
> implemented** in the current code (there is no global 401 interceptor; `apiFetch` throws
> `ApiError` and callers surface it via React Query). Left as-is (missing feature, not a defect);
> not tested and not filed as a bug.