Files
nexus/frontend/tests/contracts.test.ts
T
AzuTear cd8c78d165
CI - Build & Test / Backend (.NET) (push) Successful in 45s
CI - Build & Test / Backend integration (PostgreSQL/Toxiproxy) (push) Failing after 1m0s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m49s
CI - Build & Test / Security Check (push) Successful in 7s
CI - Build & Test / Deploy Nexus (push) Has been skipped
feat(stability): unify readiness and recovery
2026-08-01 01:21:33 +02:00

72 lines
2.5 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { AppProblem, ApiProblem, asProblemDetails, toAppProblem } from '../src/api/contracts'
describe('AppProblem', () => {
it('preserves structured conflict recovery metadata', () => {
const problem = new AppProblem(409, {
code: 'conflict',
detail: 'The resource changed.',
traceId: 'trace-1',
operationId: 'operation-1',
currentRevision: 7,
}, 'fallback')
expect(problem.kind).toBe('conflict')
expect(problem.recoveryAction).toBe('reload-conflict')
expect(problem.traceId).toBe('trace-1')
expect(problem.operationId).toBe('operation-1')
expect(problem.currentRevision).toBe(7)
})
it('maps network failures to a recoverable offline state', () => {
const problem = toAppProblem(new TypeError('Failed to fetch'))
expect(problem.kind).toBe('offline')
expect(problem.recoveryAction).toBe('inspect-connection')
})
it('does not turn missing conflict metadata into revision zero', () => {
const problem = new AppProblem(409, { code: 'conflict' }, 'fallback')
expect(problem.currentRevision).toBeNull()
expect(problem.retryAfterSeconds).toBeNull()
})
it('keeps the legacy ApiProblem compatible with the common contract', () => {
const problem = new ApiProblem(401, { detail: 'Session expired.' }, 'fallback')
expect(problem).toBeInstanceOf(AppProblem)
expect(problem.kind).toBe('authentication')
expect(problem.recoveryAction).toBe('reauthenticate')
})
it('normalizes a legacy message payload at the frontend boundary', () => {
const problem = asProblemDetails({ message: 'Gateway unavailable.' }, 503)
expect(problem?.detail).toBe('Gateway unavailable.')
expect(problem?.status).toBe(503)
})
it('normalizes a legacy error payload without replacing structured details', () => {
expect(asProblemDetails({ error: 'Legacy error.' }, 400)?.detail).toBe('Legacy error.')
expect(asProblemDetails({ detail: 'Canonical.', error: 'Legacy.' }, 409)?.detail).toBe('Canonical.')
})
it.each([
[401, 'authentication', 'reauthenticate'],
[403, 'permission', 'none'],
[409, 'conflict', 'reload-conflict'],
[429, 'rate-limit', 'retry'],
[503, 'offline', 'inspect-connection'],
[504, 'timeout', 'retry'],
] as const)(
'maps HTTP %s to the %s recovery presentation',
(status, kind, recoveryAction) => {
const problem = new AppProblem(status, null, `HTTP ${status}`)
expect(problem.kind).toBe(kind)
expect(problem.recoveryAction).toBe(recoveryAction)
},
)
})