fix(websearch): reject blank searxng status urls

This commit is contained in:
Tam Nhu Tran
2026-04-12 14:46:56 -04:00
parent 54791b5cca
commit 8d8f4469b6
2 changed files with 56 additions and 1 deletions
+2 -1
View File
@@ -29,7 +29,8 @@ function hasEnvValue(name: string): boolean {
}
function hasValidSearxngUrl(url: string | undefined): boolean {
return normalizeSearxngBaseUrl(url) !== null;
const normalized = normalizeSearxngBaseUrl(url);
return normalized !== null && normalized !== '';
}
function getProviderStatePath(): string {
+54
View File
@@ -169,6 +169,60 @@ describe('websearch readiness', () => {
}
});
it('marks SearXNG as unavailable when enabled with a blank URL', () => {
const getConfigSpy = spyOn(unifiedConfigLoader, 'getWebSearchConfig').mockReturnValue({
enabled: true,
providers: {
exa: { enabled: false, max_results: 5 },
tavily: { enabled: false, max_results: 5 },
brave: { enabled: false, max_results: 5 },
searxng: {
enabled: true,
url: '',
max_results: 5,
},
duckduckgo: { enabled: false, max_results: 5 },
gemini: { enabled: false },
grok: { enabled: false },
opencode: { enabled: false },
},
} as any);
const apiKeySpy = spyOn(providerSecrets, 'getWebSearchApiKeyStates').mockReturnValue({
exa: { envVar: 'EXA_API_KEY', configured: false, available: false, source: 'none' },
tavily: { envVar: 'TAVILY_API_KEY', configured: false, available: false, source: 'none' },
brave: { envVar: 'BRAVE_API_KEY', configured: false, available: false, source: 'none' },
});
const geminiStatusSpy = spyOn(geminiCli, 'getGeminiCliStatus').mockReturnValue({
installed: false,
version: null,
} as any);
const geminiAuthSpy = spyOn(geminiCli, 'isGeminiAuthenticated').mockReturnValue(false);
const grokStatusSpy = spyOn(grokCli, 'getGrokCliStatus').mockReturnValue({
installed: false,
version: null,
} as any);
const opencodeStatusSpy = spyOn(opencodeCli, 'getOpenCodeCliStatus').mockReturnValue({
installed: false,
version: null,
} as any);
try {
const providers = getWebSearchCliProviders();
const searxng = providers.find((entry) => entry.id === 'searxng');
expect(searxng?.enabled).toBe(true);
expect(searxng?.available).toBe(false);
expect(searxng?.detail).toContain('Set a valid SearXNG base URL');
} finally {
getConfigSpy.mockRestore();
apiKeySpy.mockRestore();
geminiStatusSpy.mockRestore();
geminiAuthSpy.mockRestore();
grokStatusSpy.mockRestore();
opencodeStatusSpy.mockRestore();
}
});
it('treats cooled-down providers as temporarily unavailable in readiness status', () => {
const tempHome = mkdtempSync(join(tmpdir(), 'websearch-status-cooldown-'));
const statePath = join(tempHome, '.ccs', 'cache', 'websearch-provider-state.json');