mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-16 20:22:21 +03:00
fix(providers): preserve connection test status codes (#10272)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- **fix(providers):** preserve validator HTTP status codes in API-key and web connection-test results so callers can distinguish authentication, rate-limit, and upstream failures ([#10272](https://github.com/diegosouzapw/OmniRoute/pull/10272)) — thanks @Zartharas
|
||||
28
src/app/api/providers/[id]/test/apiKeyTestResult.ts
Normal file
28
src/app/api/providers/[id]/test/apiKeyTestResult.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export interface ApiKeyValidationResult {
|
||||
valid: boolean;
|
||||
warning?: string | null;
|
||||
statusCode?: number | null;
|
||||
deployments?: unknown;
|
||||
}
|
||||
|
||||
export interface ApiKeyTestDiagnosis {
|
||||
type: string;
|
||||
source: string;
|
||||
message: string | null;
|
||||
code: string | null;
|
||||
}
|
||||
|
||||
export function buildApiKeyConnectionTestResult(
|
||||
result: ApiKeyValidationResult,
|
||||
error: string | null,
|
||||
diagnosis: ApiKeyTestDiagnosis
|
||||
) {
|
||||
return {
|
||||
valid: !!result.valid,
|
||||
error,
|
||||
warning: result.warning || null,
|
||||
statusCode: result.valid ? null : (result.statusCode ?? null),
|
||||
diagnosis,
|
||||
...(Array.isArray(result.deployments) ? { deployments: result.deployments } : {}),
|
||||
};
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import { isGitLabDirectAccessDisabled } from "@/lib/oauth/gitlab";
|
||||
import { providerAllowsOptionalApiKey } from "@/shared/constants/providers";
|
||||
import { removeConnectionHealth } from "@omniroute/open-sse/services/apiKeyRotator.ts";
|
||||
import { classifyAmbiguousOrAuthError, type ClassifyFailureArgs } from "./mistralAmbiguousAuth";
|
||||
import { buildApiKeyConnectionTestResult } from "./apiKeyTestResult";
|
||||
import { OAUTH_TEST_CONFIG } from "./oauthTestConfig";
|
||||
|
||||
// Bound the OAuth probe so a hung upstream can't block the connection-test queue
|
||||
@@ -614,15 +615,7 @@ async function testApiKeyConnection(connection: any) {
|
||||
? makeDiagnosis("ok", "upstream", null, null)
|
||||
: classifyFailure({ error, statusCode: result.statusCode, provider: connection.provider });
|
||||
|
||||
return {
|
||||
valid: !!result.valid,
|
||||
error,
|
||||
warning: result.warning || null,
|
||||
diagnosis,
|
||||
...(Array.isArray((result as any).deployments)
|
||||
? { deployments: (result as any).deployments }
|
||||
: {}),
|
||||
};
|
||||
return buildApiKeyConnectionTestResult(result, error, diagnosis);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -709,7 +702,8 @@ export async function testSingleConnection(connectionId: string, validationModel
|
||||
// failures a short cooldown so the lazy-recovery path retries them.
|
||||
const terminalTestStatuses = new Set(["banned", "expired", "credits_exhausted"]);
|
||||
const isTerminalFailure =
|
||||
!result.valid && terminalTestStatuses.has(String(diagnosis.code ?? diagnosis.type ?? "").toLowerCase());
|
||||
!result.valid &&
|
||||
terminalTestStatuses.has(String(diagnosis.code ?? diagnosis.type ?? "").toLowerCase());
|
||||
const testFailureCooldownMs = result.valid ? 0 : 30_000; // 30s retry window
|
||||
|
||||
const updateData: Record<string, any> = {
|
||||
|
||||
68
tests/unit/provider-test-statuscode-propagation.test.ts
Normal file
68
tests/unit/provider-test-statuscode-propagation.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { buildApiKeyConnectionTestResult } =
|
||||
await import("../../src/app/api/providers/[id]/test/apiKeyTestResult.ts");
|
||||
|
||||
const FAILURE_DIAGNOSIS = {
|
||||
type: "synthetic_failure",
|
||||
source: "test",
|
||||
message: "Synthetic validator failure",
|
||||
code: "synthetic",
|
||||
};
|
||||
|
||||
test("API-key connection tests preserve validator failure status codes", () => {
|
||||
for (const statusCode of [401, 403, 429, 503]) {
|
||||
const result = buildApiKeyConnectionTestResult(
|
||||
{
|
||||
valid: false,
|
||||
warning: null,
|
||||
statusCode,
|
||||
},
|
||||
`Synthetic validator failure ${statusCode}`,
|
||||
FAILURE_DIAGNOSIS
|
||||
);
|
||||
|
||||
assert.equal(result.valid, false);
|
||||
assert.equal(result.statusCode, statusCode);
|
||||
assert.deepEqual(result.diagnosis, FAILURE_DIAGNOSIS);
|
||||
}
|
||||
});
|
||||
|
||||
test("status-less semantic failures remain status-less", () => {
|
||||
const result = buildApiKeyConnectionTestResult(
|
||||
{
|
||||
valid: false,
|
||||
warning: null,
|
||||
},
|
||||
"Synthetic semantic failure",
|
||||
FAILURE_DIAGNOSIS
|
||||
);
|
||||
|
||||
assert.equal(result.valid, false);
|
||||
assert.equal(result.statusCode, null);
|
||||
assert.deepEqual(result.diagnosis, FAILURE_DIAGNOSIS);
|
||||
});
|
||||
|
||||
test("successful validation does not synthesize an HTTP status", () => {
|
||||
const diagnosis = {
|
||||
type: "ok",
|
||||
source: "upstream",
|
||||
message: null,
|
||||
code: null,
|
||||
};
|
||||
|
||||
const result = buildApiKeyConnectionTestResult(
|
||||
{
|
||||
valid: true,
|
||||
warning: null,
|
||||
statusCode: 200,
|
||||
},
|
||||
null,
|
||||
diagnosis
|
||||
);
|
||||
|
||||
assert.equal(result.valid, true);
|
||||
assert.equal(result.statusCode, null);
|
||||
assert.deepEqual(result.diagnosis, diagnosis);
|
||||
});
|
||||
Reference in New Issue
Block a user