Persiste proposal, specs delta (auth, api-client, access-requests, platform-shell, testing), design con 8 ADRs, tasks (27 items en 10 fases), verify-report (28 tests, 0 críticos, 0 warnings tras el cierre) y archive-report final. Cierra el ciclo SDD del cleanup de deuda técnica del console.
119 lines
5.0 KiB
Markdown
119 lines
5.0 KiB
Markdown
# 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
|