Adds a small test harness (src/test/testUtils.tsx):
- renderWithProviders mounts a fresh QueryClient (no retries, no cache)
+ MemoryRouter so screens using useNavigate / <Link> don't crash.
- installFetchMock(responses[]) replaces globalThis.fetch with a typed
sequence of canned responses and records call URLs + init.
Specs (10 cases, all green):
LoginPage.test.tsx
- happy path: submit posts to /api/v1/auth/login with credentials:'include',
correct JSON body shape (username/password).
- 401 surfaces "Identifiants invalides" and does NOT leak the backend
detail string.
- empty submit is intercepted by HTML5 `required` — no fetch fires.
EngagementsPage.test.tsx
- loading row renders while /engagements is in flight.
- empty state renders on 200 [].
- error state + Retry button render on 500.
- populated table renders the snake_case fields correctly (name,
client_name, c2_type uppercased).
EngagementCreateDialog.test.tsx
- client-side validation: empty name blocks submission, no fetch fires.
- 422 Pydantic error on the `name` field maps to the inline message
next to the input.
- 201 success triggers onClose() and POSTs to /api/v1/engagements.