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
72 lines
2.5 KiB
TypeScript
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)
|
|
},
|
|
)
|
|
})
|