fix(api): unwrap downstream key unique violations
This commit is contained in:
@@ -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 () => {
|
||||
const inserted = await db.insert(schema.downstreamApiKeys).values({
|
||||
name: 'analytics-key',
|
||||
|
||||
@@ -20,13 +20,42 @@ function validateKeyShape(key: string): boolean {
|
||||
return key.startsWith('sk-') && key.length >= 6;
|
||||
}
|
||||
|
||||
function looksLikeUniqueViolation(error: unknown): boolean {
|
||||
const message = (error as Error | undefined)?.message || '';
|
||||
if (runtimeDbDialect === 'postgres') {
|
||||
return message.includes('duplicate key value violates unique constraint') &&
|
||||
message.includes('downstream_api_keys_key_unique');
|
||||
type ErrorLike = {
|
||||
message?: string;
|
||||
code?: string | number;
|
||||
cause?: unknown;
|
||||
};
|
||||
|
||||
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[] {
|
||||
|
||||
Reference in New Issue
Block a user