fix(api): unwrap downstream key unique violations

This commit is contained in:
ciat-777
2026-03-21 13:26:16 +08:00
parent 87cf54140c
commit e454ec18ec
2 changed files with 73 additions and 6 deletions
@@ -314,6 +314,44 @@ describe('downstream api keys routes', () => {
}); });
}); });
it('rejects duplicate key update with conflict status', async () => {
const firstRes = await app.inject({
method: 'POST',
url: '/api/downstream-keys',
payload: {
name: 'dup-a',
key: 'sk-dup-key-001',
},
});
expect(firstRes.statusCode).toBe(200);
const secondRes = await app.inject({
method: 'POST',
url: '/api/downstream-keys',
payload: {
name: 'dup-b',
key: 'sk-dup-key-002',
},
});
expect(secondRes.statusCode).toBe(200);
const secondId = secondRes.json().item.id as number;
const duplicateUpdateRes = await app.inject({
method: 'PUT',
url: `/api/downstream-keys/${secondId}`,
payload: {
name: 'dup-b-updated',
key: 'sk-dup-key-001',
},
});
expect(duplicateUpdateRes.statusCode).toBe(409);
expect(duplicateUpdateRes.json()).toMatchObject({
success: false,
message: 'API key 已存在',
});
});
it('returns summary, overview and trend aggregated from proxy logs', async () => { it('returns summary, overview and trend aggregated from proxy logs', async () => {
const inserted = await db.insert(schema.downstreamApiKeys).values({ const inserted = await db.insert(schema.downstreamApiKeys).values({
name: 'analytics-key', name: 'analytics-key',
+35 -6
View File
@@ -20,13 +20,42 @@ function validateKeyShape(key: string): boolean {
return key.startsWith('sk-') && key.length >= 6; return key.startsWith('sk-') && key.length >= 6;
} }
function looksLikeUniqueViolation(error: unknown): boolean { type ErrorLike = {
const message = (error as Error | undefined)?.message || ''; message?: string;
if (runtimeDbDialect === 'postgres') { code?: string | number;
return message.includes('duplicate key value violates unique constraint') && cause?: unknown;
message.includes('downstream_api_keys_key_unique'); };
function getErrorChain(error: unknown): ErrorLike[] {
const chain: ErrorLike[] = [];
const seen = new Set<unknown>();
let current: unknown = error;
while (current && typeof current === 'object' && !seen.has(current)) {
seen.add(current);
chain.push(current as ErrorLike);
current = (current as ErrorLike).cause;
} }
return message.includes('UNIQUE constraint failed') && message.includes('downstream_api_keys.key'); return chain;
}
function looksLikeUniqueViolation(error: unknown): boolean {
const chain = getErrorChain(error);
if (runtimeDbDialect === 'postgres') {
return chain.some((entry) => {
const message = entry.message || '';
const code = String(entry.code || '');
return code === '23505'
|| (message.includes('duplicate key value violates unique constraint')
&& message.includes('downstream_api_keys_key_unique'));
});
}
return chain.some((entry) => {
const message = entry.message || '';
const code = String(entry.code || '');
return code === 'SQLITE_CONSTRAINT'
|| code === 'SQLITE_CONSTRAINT_UNIQUE'
|| (message.includes('UNIQUE constraint failed') && message.includes('downstream_api_keys.key'));
});
} }
function normalizeBatchIds(raw: unknown): number[] { function normalizeBatchIds(raw: unknown): number[] {