import axios, { AxiosHeaders } from 'axios' import { afterEach, describe, expect, it, vi } from 'vitest' import { ApiResponseError, getApiErrorDetails, http, registerAuthRefreshHandler, registerAuthSessionExpiredHandler, unwrapApiResponse, } from './http' const originalAdapter = http.defaults.adapter afterEach(() => { http.defaults.adapter = originalAdapter registerAuthRefreshHandler(async () => { throw new Error('refresh handler is not configured') }) registerAuthSessionExpiredHandler(() => undefined) }) describe('API error diagnostics', () => { it('treats only body code 200 as success', () => { expect(unwrapApiResponse({ code: '200', message: 'success', data: { id: '1' } })) .toEqual({ id: '1' }) expect(() => unwrapApiResponse({ code: '0', message: 'legacy success code', data: null })) .toThrow(ApiResponseError) }) it('keeps business request and trace identifiers when unwrapping fails', () => { expect(() => unwrapApiResponse({ code: 'A0403', message: '无权限', data: null, requestId: 'request-1', traceId: 'trace-1', })).toThrow(ApiResponseError) try { unwrapApiResponse({ code: 'A0403', message: '无权限', data: null, requestId: 'request-1', traceId: 'trace-1' }) } catch (error) { expect(getApiErrorDetails(error)).toEqual({ message: '无权限', code: 'A0403', requestId: 'request-1', traceId: 'trace-1', }) } }) it('extracts identifiers from an HTTP error envelope', () => { const error = new axios.AxiosError('Request failed', 'ERR_BAD_RESPONSE', undefined, undefined, { status: 503, statusText: 'Service Unavailable', headers: {}, config: { headers: new AxiosHeaders() }, data: { code: 'S0503', message: '服务不可用', requestId: 'request-2', traceId: 'trace-2' }, }) expect(getApiErrorDetails(error)).toEqual({ message: '服务不可用', code: 'S0503', requestId: 'request-2', traceId: 'trace-2', }) }) it('explains a proxy 413 response without a JSON error envelope', () => { const error = new axios.AxiosError('Request failed', 'ERR_BAD_REQUEST', undefined, undefined, { status: 413, statusText: 'Payload Too Large', headers: {}, config: { headers: new AxiosHeaders() }, data: 'Request Entity Too Large', }) expect(getApiErrorDetails(error)).toEqual({ message: '上传文件的请求体过大,服务器已拒绝;请压缩后重试,如文件未超过 5MB,请联系管理员检查网关上传限制', code: 'HTTP_413', requestId: undefined, traceId: undefined, }) }) it('notifies session expiry once when concurrent 401 requests share a failed refresh', async () => { const refresh = vi.fn(async () => { throw new Error('refresh token expired') }) const expired = vi.fn() registerAuthRefreshHandler(refresh) registerAuthSessionExpiredHandler(expired) http.defaults.adapter = async (config) => { throw new axios.AxiosError('Unauthorized', 'ERR_BAD_REQUEST', config, undefined, { status: 401, statusText: 'Unauthorized', headers: {}, config, data: { code: 'A0401', message: '登录状态已过期' }, }) } const results = await Promise.allSettled([ http.get('/platform/v1/orders'), http.get('/platform/v1/warehouses'), ]) expect(results.every((result) => result.status === 'rejected')).toBe(true) expect(refresh).toHaveBeenCalledTimes(1) expect(expired).toHaveBeenCalledTimes(1) }) })