# Testing Baseline Specification # New Spec for: Testing Infrastructure — console-tech-debt-cleanup ## Purpose Establishes Vitest + Testing Library as the test runner in `services/frontend-console` and defines the minimum baseline suite that must pass before any P0/P1 change is merged. ## Requirements ### Requirement: Vitest + Testing Library Configured `services/frontend-console` MUST have Vitest and `@testing-library/react` installed and configured. The configuration MUST mirror `services/frontend-admin` (`jsdom` environment, `setupTests.ts` importing `@testing-library/jest-dom`). `pnpm test` MUST execute the suite and exit non-zero on failure. `pnpm test:watch` MUST run in watch mode. #### Scenario: pnpm test runs green on a clean checkout - GIVEN a developer runs `pnpm install && pnpm test` inside `services/frontend-console` - WHEN all baseline tests pass - THEN the process exits with code 0 - AND no test is skipped #### Scenario: pnpm test fails on a broken test - GIVEN a test assertion fails - WHEN `pnpm test` is run - THEN the process exits with non-zero code - AND the failing test name and assertion are printed to stdout --- ### Requirement: SessionCoordinator Refresh Deduplication Test The test suite MUST include a test that verifies `SessionCoordinator.refresh()` calls `fetch` (the HTTP POST to `/auth/refresh`) exactly once when two concurrent invocations are made. #### Scenario: Two concurrent refresh calls share one HTTP request - GIVEN `fetch` is mocked to resolve after a short delay with `{ access_token: "new-token", role: "platform_admin", email: "op@x.com" }` - WHEN `SessionCoordinator.refresh()` is called twice concurrently (without awaiting the first) - THEN `fetch` mock is called exactly once - AND both calls resolve with the same token string --- ### Requirement: apiClient 401 Retry Path Test The test suite MUST include tests for `apiClient`'s 401 handling covering: single refresh-and-retry success, and refresh failure leading to logout + redirect. #### Scenario: apiClient retries after 401 and resolves on success - GIVEN `fetch` returns 401 on first call and 200 with body on retry - AND `SessionCoordinator.refresh` resolves with a new token - WHEN `apiClient('/resource', { method: 'GET' })` is called - THEN the resolved value equals the parsed body from the 200 response - AND `fetch` was called twice total #### Scenario: apiClient calls logout on refresh failure - GIVEN `fetch` returns 401 and `SessionCoordinator.refresh` returns null - WHEN `apiClient('/resource', { method: 'GET' })` is called - THEN `useAuthStore.getState().logout` is called - AND the function throws --- ### Requirement: Auth Store setSession and logout Tests The test suite MUST cover `setSession` writing to `accessToken` (and only `accessToken` for the token), and `logout` resetting `accessToken` and `user` to null. #### Scenario: setSession writes accessToken - GIVEN the store is freshly initialized - WHEN `setSession({ accessToken: 'tok', refreshToken: null, role: 'platform_admin', email: 'op@x.com' })` is called - THEN `getState().accessToken` equals `'tok'` - AND `getState().user` equals `{ email: 'op@x.com', role: 'platform_admin' }` - AND there is no `token` field with a non-null value on the state (field absent or null) #### Scenario: logout clears state - GIVEN the store has a non-null `accessToken` and `user` - WHEN `logout()` is called - THEN `getState().accessToken` is null - AND `getState().user` is null --- ### Requirement: AccessRequestsPage Mutation Baseline Tests The test suite MUST include component tests for `AccessRequestsPage` covering the happy path and error path for each of the three mutations (approve, reject, mark-as-review). #### Scenario: Approve mutation — success toast and invalidation - GIVEN `AccessRequestsPage` renders with a request in `pending` status - WHEN the operator selects that request and clicks "Aprobar" - AND `approveAccessRequest` resolves successfully - THEN `toast.success` is called with a message containing "aprobada" - AND `queryClient.invalidateQueries` is called with key `['platform-access-requests']` #### Scenario: Reject mutation — success toast, modal closes, invalidation - GIVEN the rejection modal is open with a valid reason - WHEN `rejectAccessRequest` resolves successfully - THEN `toast.success` is called with a message containing "rechazada" - AND the rejection modal is no longer visible - AND the query cache is invalidated #### Scenario: Review mutation — success toast and invalidation - GIVEN a request with `status = 'pending'` is selected - WHEN the operator clicks "Revisar" - AND `markAccessRequestInReview` resolves successfully - THEN `toast.success` is called with a message containing "revision" - AND the query cache is invalidated #### Scenario: Mutation error — error toast, no invalidation - GIVEN any mutation is triggered - WHEN the API call rejects with `new Error('Network error')` - THEN `toast.error` is called with `'Network error'` - AND `queryClient.invalidateQueries` is NOT called