refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)

配置 SSOT(TOML + .env)
统一错误契约
Auth 与事务边界
Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client
可观测性(OpenTelemetry + LGTM)
This commit is contained in:
Sully
2026-05-22 13:44:50 +08:00
committed by GitHub
parent f09ae248f9
commit 53e0065e3e
298 changed files with 15247 additions and 4344 deletions

View File

@@ -0,0 +1,55 @@
import { parseApiError } from '@/core/api/parseApiError';
describe('parseApiError', () => {
it('prefers unified message field', () => {
expect(
parseApiError(
{
error_code: 'NOT_FOUND',
message: '资源不存在',
request_id: 'r1',
},
'fallback',
),
).toEqual({
message: '资源不存在',
errorCode: 'NOT_FOUND',
requestId: 'r1',
});
});
it('falls back to legacy detail string', () => {
expect(parseApiError({ detail: '请求无效' }, 'HTTP 400')).toEqual({
message: '请求无效',
errorCode: undefined,
requestId: undefined,
});
});
it('parses legacy detail validation list', () => {
expect(
parseApiError(
{
detail: [{ loc: ['body', 'phone'], msg: 'field required' }],
},
'HTTP 422',
),
).toEqual({
message: 'field required',
errorCode: undefined,
requestId: undefined,
});
});
it('uses fallback when message is missing', () => {
expect(parseApiError({ error_code: 'INTERNAL_ERROR' }, 'HTTP 500')).toEqual({
message: 'HTTP 500',
errorCode: 'INTERNAL_ERROR',
requestId: undefined,
});
});
it('uses fallback when body is null', () => {
expect(parseApiError(null, 'HTTP 502')).toEqual({ message: 'HTTP 502' });
});
});

View File

@@ -0,0 +1,46 @@
import {
resetRefreshLockForTests,
withRefreshLock,
} from '@/core/auth/refresh-lock';
describe('withRefreshLock', () => {
beforeEach(() => {
resetRefreshLockForTests();
});
it('runs concurrent callers through a single in-flight refresh', async () => {
let runs = 0;
const fn = jest.fn(async () => {
runs += 1;
await new Promise((resolve) => setTimeout(resolve, 20));
return 'ok';
});
const [a, b, c] = await Promise.all([
withRefreshLock(fn),
withRefreshLock(fn),
withRefreshLock(fn),
]);
expect(a).toBe('ok');
expect(b).toBe('ok');
expect(c).toBe('ok');
expect(fn).toHaveBeenCalledTimes(1);
expect(runs).toBe(1);
});
it('propagates errors to queued waiters', async () => {
const fn = jest.fn(async () => {
throw new Error('refresh failed');
});
const results = await Promise.allSettled([
withRefreshLock(fn),
withRefreshLock(fn),
]);
expect(fn).toHaveBeenCalledTimes(1);
expect(results[0].status).toBe('rejected');
expect(results[1].status).toBe('rejected');
});
});