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.
3.3 KiB
API Client Specification
Delta for: API Client — console-tech-debt-cleanup
ADDED Requirements
Requirement: No null-as-T Casts; Typed 204 Path
apiClient<T> MUST NOT contain null as T. For endpoints that legitimately return HTTP 204 (no body), callers MUST use a dedicated apiClientVoid (or equivalent) that returns Promise<void>. apiClient<T> MUST throw or return a typed error if invoked against an endpoint that responds 204. TypeScript MUST be able to statically distinguish body-returning calls from void calls.
Scenario: 200 response with body
- GIVEN a call
apiClient<User>('/users/1', { method: 'GET' }) - WHEN the server responds HTTP 200 with a JSON body
{ id: "...", email: "..." } - THEN the function resolves with
Usertyped data - AND no cast is required at the call site
Scenario: 204 response via apiClientVoid
- GIVEN a call
apiClientVoid('/access-requests/123/approve', { method: 'PUT' }) - WHEN the server responds HTTP 204
- THEN the function resolves with
void(no body) - AND the TypeScript return type is
Promise<void>— notPromise<null>orPromise<undefined>
Scenario: Caller cannot pass 204-returning endpoint to apiClient
- GIVEN a developer writes
apiClient<void>('/approve', ...) - WHEN TypeScript compiles
- THEN a compile-time error surfaces (or the function signature prevents the
voidgeneric)
Requirement: 401 Triggers Single Refresh-and-Retry
When apiClient receives HTTP 401 on the first attempt (non-bypassed endpoint, non-retry call), it MUST invoke SessionCoordinator.refresh() exactly once, then replay the original request with the new token. If the retried request succeeds, apiClient MUST resolve with the response body. If refresh fails or the retry also fails, apiClient MUST call logout(), redirect to /login, and throw.
Scenario: 401 with refresh success, retry succeeds with body
- GIVEN an authenticated call returns HTTP 401
- AND
SessionCoordinator.refresh()returns a new access token - WHEN the retried request returns HTTP 200 with body
- THEN
apiClientresolves with the parsed response body - AND only one refresh call was made
Scenario: 401 with refresh success, retry returns 204
- GIVEN an authenticated call returns HTTP 401
- AND
SessionCoordinator.refresh()returns a new access token - WHEN the retried request returns HTTP 204
- THEN
apiClientVoidresolves withvoid - AND the caller is NOT handed a null value
Scenario: 401 with refresh failure
- GIVEN an authenticated call returns HTTP 401
- AND
SessionCoordinator.refresh()returns null - THEN
logout()is called - AND the browser is redirected to
/login - AND
apiClientthrows with message "Sesion expirada"
Requirement: SessionCoordinator Deduplicates Concurrent Refresh Calls
If multiple in-flight requests trigger a 401 simultaneously, SessionCoordinator.refresh() MUST execute the underlying HTTP refresh request exactly once. All concurrent callers MUST await the same Promise and receive the same new token.
Scenario: Two concurrent 401s produce one refresh HTTP call
- GIVEN two
apiClientcalls are in flight simultaneously - WHEN both receive HTTP 401
- THEN
SessionCoordinator.performRefreshis called exactly once (one HTTP POST to/auth/refresh) - AND both original requests are retried with the same new token