From edf8dd2a12ace1c6d990b69dce362fd9a66a8c14 Mon Sep 17 00:00:00 2001 From: Jack Cowey Date: Wed, 18 Mar 2026 12:29:14 +0000 Subject: [PATCH 1/2] fix(search): accept authenticated serper validation responses Treat non-auth Serper validation errors as successful authentication so valid API keys are not rejected during provider setup. Made-with: Cursor --- src/lib/providers/validation.ts | 6 +++ .../unit/search-provider-validation.test.mjs | 50 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/unit/search-provider-validation.test.mjs diff --git a/src/lib/providers/validation.ts b/src/lib/providers/validation.ts index 083f41c078..050538db2e 100644 --- a/src/lib/providers/validation.ts +++ b/src/lib/providers/validation.ts @@ -452,6 +452,12 @@ async function validateSearchProvider( if (response.status === 401 || response.status === 403) { return { valid: false, error: "Invalid API key" }; } + // For provider setup we only need to confirm authentication passed. + // Search providers may return non-auth statuses for exhausted credits, + // rate limiting, or request-shape quirks while still accepting the key. + if (response.status < 500) { + return { valid: true, error: null }; + } return { valid: false, error: `Validation failed: ${response.status}` }; } catch (error: any) { return { valid: false, error: error.message || "Validation failed" }; diff --git a/tests/unit/search-provider-validation.test.mjs b/tests/unit/search-provider-validation.test.mjs new file mode 100644 index 0000000000..f05aa51864 --- /dev/null +++ b/tests/unit/search-provider-validation.test.mjs @@ -0,0 +1,50 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts"); + +test("serper validation accepts authenticated non-auth upstream errors", async () => { + const originalFetch = globalThis.fetch; + + globalThis.fetch = async () => + new Response(JSON.stringify({ error: "credits_exhausted" }), { + status: 402, + headers: { "content-type": "application/json" }, + }); + + try { + const result = await validateProviderApiKey({ + provider: "serper-search", + apiKey: "valid-serper-key", + }); + + assert.equal(result.valid, true); + assert.equal(result.error, null); + assert.equal(result.unsupported, false); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("serper validation still rejects unauthorized keys", async () => { + const originalFetch = globalThis.fetch; + + globalThis.fetch = async () => + new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 403, + headers: { "content-type": "application/json" }, + }); + + try { + const result = await validateProviderApiKey({ + provider: "serper-search", + apiKey: "bad-serper-key", + }); + + assert.equal(result.valid, false); + assert.equal(result.error, "Invalid API key"); + assert.equal(result.unsupported, false); + } finally { + globalThis.fetch = originalFetch; + } +}); From b2dc53d18ba3c350523346d60652f1d11582223f Mon Sep 17 00:00:00 2001 From: Jack Cowey Date: Wed, 18 Mar 2026 12:55:25 +0000 Subject: [PATCH 2/2] fix(search): return consistent validation result shape Keep search provider validation responses consistent with other validators so Serper regression tests and CI assertions can rely on unsupported=false. Made-with: Cursor --- src/lib/providers/validation.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/providers/validation.ts b/src/lib/providers/validation.ts index 050538db2e..5825d71308 100644 --- a/src/lib/providers/validation.ts +++ b/src/lib/providers/validation.ts @@ -445,22 +445,22 @@ async function validateAnthropicCompatibleProvider({ apiKey, providerSpecificDat async function validateSearchProvider( url: string, init: RequestInit -): Promise<{ valid: boolean; error: string | null }> { +): Promise<{ valid: boolean; error: string | null; unsupported: false }> { try { const response = await fetch(url, init); - if (response.ok) return { valid: true, error: null }; + if (response.ok) return { valid: true, error: null, unsupported: false }; if (response.status === 401 || response.status === 403) { - return { valid: false, error: "Invalid API key" }; + return { valid: false, error: "Invalid API key", unsupported: false }; } // For provider setup we only need to confirm authentication passed. // Search providers may return non-auth statuses for exhausted credits, // rate limiting, or request-shape quirks while still accepting the key. if (response.status < 500) { - return { valid: true, error: null }; + return { valid: true, error: null, unsupported: false }; } - return { valid: false, error: `Validation failed: ${response.status}` }; + return { valid: false, error: `Validation failed: ${response.status}`, unsupported: false }; } catch (error: any) { - return { valid: false, error: error.message || "Validation failed" }; + return { valid: false, error: error.message || "Validation failed", unsupported: false }; } }