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) }, ) })