Files
OmniOilPersonal/openspec/changes/archive/2026-05-07-console-tech-debt-cleanup/specs/testing/spec.md
henryor af5535dfcc docs(openspec): archiva el change console-tech-debt-cleanup
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.
2026-05-07 21:14:56 -05:00

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