diff --git a/src/lib/providers/validation/webProvidersA.ts b/src/lib/providers/validation/webProvidersA.ts index 1a6aa00380..a1b20f1df9 100644 --- a/src/lib/providers/validation/webProvidersA.ts +++ b/src/lib/providers/validation/webProvidersA.ts @@ -116,6 +116,7 @@ export async function validateDeepSeekWebProvider({ apiKey }: any) { return { valid: false, error: "userToken is invalid or expired — get a fresh one from localStorage", + statusCode: resp.status, }; } if (!resp.ok) { @@ -123,6 +124,20 @@ export async function validateDeepSeekWebProvider({ apiKey }: any) { } const json = await resp.json(); const bizData = json?.data?.biz_data || json?.biz_data; + + // DeepSeek's web endpoint can report auth rejection as HTTP 200 with an + // application-level error envelope. Code 40003 is the observed + // "Authorization Failed" signal. Preserve the real HTTP behavior while + // returning an auth-classifiable status to OmniRoute's connection-test + // layer so it is not collapsed into a generic upstream_error. + if (Number(json?.code) === 40003) { + return { + valid: false, + error: "userToken is invalid or expired — get a fresh one from localStorage", + statusCode: 401, + }; + } + if (!bizData?.token) { return { valid: false, diff --git a/tests/unit/deepseek-web-auth-semantics.test.ts b/tests/unit/deepseek-web-auth-semantics.test.ts new file mode 100644 index 0000000000..2e34a87dac --- /dev/null +++ b/tests/unit/deepseek-web-auth-semantics.test.ts @@ -0,0 +1,132 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { validateDeepSeekWebProvider } = await import( + "../../src/lib/providers/validation/webProvidersA.ts" +); +const { classifyFailure } = await import( + "../../src/app/api/providers/[id]/test/route.ts" +); + +const SYNTHETIC_INVALID_TOKEN = "omniroute-auth-keeper-invalid-session-deepseek"; + +type DeepSeekValidationResult = Awaited>; + +function statusCodeOf(result: DeepSeekValidationResult): number | undefined { + if ("statusCode" in result && typeof result.statusCode === "number") { + return result.statusCode; + } + return undefined; +} + +async function withFetchResponse(body: unknown, status: number, fn: () => Promise) { + const originalFetch = globalThis.fetch; + globalThis.fetch = async () => + new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); + + try { + await fn(); + } finally { + globalThis.fetch = originalFetch; + } +} + +test("DeepSeek HTTP 200 code 40003 is surfaced as auth-classifiable 401", async () => { + await withFetchResponse( + { + code: 40003, + msg: "Authorization Failed", + data: { biz_data: null }, + }, + 200, + async () => { + const result = await validateDeepSeekWebProvider({ + apiKey: SYNTHETIC_INVALID_TOKEN, + }); + const statusCode = statusCodeOf(result); + + assert.equal(result.valid, false); + assert.equal(statusCode, 401); + assert.match(String(result.error ?? ""), /invalid or expired/i); + assert.doesNotMatch(String(result.error ?? ""), new RegExp(SYNTHETIC_INVALID_TOKEN)); + + const diagnosis = classifyFailure({ + error: result.error, + statusCode, + provider: "deepseek-web", + }); + assert.equal(diagnosis.type, "upstream_auth_error"); + assert.equal(diagnosis.code, "401"); + } + ); +}); + +test("DeepSeek unknown HTTP 200 business failure is not synthesized into 401", async () => { + await withFetchResponse( + { + code: 49999, + msg: "Temporary provider condition", + data: { biz_data: null }, + }, + 200, + async () => { + const result = await validateDeepSeekWebProvider({ + apiKey: SYNTHETIC_INVALID_TOKEN, + }); + const statusCode = statusCodeOf(result); + + assert.equal(result.valid, false); + assert.equal(statusCode, undefined); + + const diagnosis = classifyFailure({ + error: result.error, + statusCode, + provider: "deepseek-web", + }); + assert.equal(diagnosis.type, "upstream_error"); + assert.equal(diagnosis.code, "upstream_error"); + } + ); +}); + +test("DeepSeek literal 401 and 403 preserve their HTTP auth status", async () => { + for (const status of [401, 403]) { + await withFetchResponse({}, status, async () => { + const result = await validateDeepSeekWebProvider({ + apiKey: SYNTHETIC_INVALID_TOKEN, + }); + const statusCode = statusCodeOf(result); + + assert.equal(result.valid, false); + assert.equal(statusCode, status); + + const diagnosis = classifyFailure({ + error: result.error, + statusCode, + provider: "deepseek-web", + }); + assert.equal(diagnosis.type, "upstream_auth_error"); + assert.equal(diagnosis.code, String(status)); + }); + } +}); + +test("DeepSeek valid HTTP 200 response with derived token remains healthy", async () => { + await withFetchResponse( + { + code: 0, + msg: "", + data: { biz_data: { token: "synthetic-derived-access-token" } }, + }, + 200, + async () => { + const result = await validateDeepSeekWebProvider({ + apiKey: "synthetic-valid-user-token", + }); + assert.deepEqual(result, { valid: true, error: null }); + } + ); +});