Files
openmaic/OpenMAIC/tests/server/web-search-config.test.ts
2026-08-16 14:58:47 +08:00

201 lines
7.2 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from 'vitest';
describe('server web search config', () => {
beforeEach(() => {
vi.resetModules();
vi.unstubAllEnvs();
delete process.env.TAVILY_API_KEY;
delete process.env.TAVILY_BASE_URL;
delete process.env.BOCHA_API_KEY;
delete process.env.BOCHA_BASE_URL;
delete process.env.BRAVE_API_KEY;
delete process.env.BRAVE_BASE_URL;
delete process.env.BAIDU_API_KEY;
delete process.env.BAIDU_BASE_URL;
delete process.env.WEB_SEARCH_MINIMAX_API_KEY;
delete process.env.WEB_SEARCH_MINIMAX_BASE_URL;
delete process.env.WEB_SEARCH_CLAUDE_API_KEY;
delete process.env.WEB_SEARCH_CLAUDE_BASE_URL;
delete process.env.WEB_SEARCH_CLAUDE_MODELS;
delete process.env.SEARXNG_BASE_URL;
});
it('rejects client-controlled base URLs outside the provider allowlist', async () => {
const { resolveSafeClientWebSearchBaseUrl } = await import('@/lib/server/web-search-config');
expect(() =>
resolveSafeClientWebSearchBaseUrl('bocha', 'http://127.0.0.1:3000/internal'),
).toThrow('Unsupported Bocha base URL');
});
it('allows official Bocha client base URLs', async () => {
const { resolveSafeClientWebSearchBaseUrl } = await import('@/lib/server/web-search-config');
expect(resolveSafeClientWebSearchBaseUrl('bocha', 'https://api.bochaai.com/v1')).toBe(
'https://api.bochaai.com/v1',
);
});
it('allows official MiniMax client base URLs', async () => {
const { resolveSafeClientWebSearchBaseUrl } = await import('@/lib/server/web-search-config');
expect(
resolveSafeClientWebSearchBaseUrl(
'minimax',
'https://api.minimaxi.com/v1/coding_plan/search',
),
).toBe('https://api.minimaxi.com/v1/coding_plan/search');
});
it('resolves classroom web search config from selected provider and client key', async () => {
const { resolveClassroomWebSearchConfig } = await import('@/lib/server/web-search-config');
expect(
resolveClassroomWebSearchConfig({
webSearchProviderId: 'bocha',
webSearchApiKey: 'bocha-client-key',
}),
).toEqual({
providerId: 'bocha',
apiKey: 'bocha-client-key',
baseUrl: undefined,
});
});
it('uses server base URL for classroom web search config instead of client-controlled URLs', async () => {
vi.stubEnv('BOCHA_API_KEY', 'bocha-server-key');
vi.stubEnv('BOCHA_BASE_URL', 'http://internal-proxy.local/bocha');
const { resolveClassroomWebSearchConfig } = await import('@/lib/server/web-search-config');
expect(resolveClassroomWebSearchConfig({ webSearchProviderId: 'bocha' })).toEqual({
providerId: 'bocha',
apiKey: 'bocha-server-key',
baseUrl: 'http://internal-proxy.local/bocha',
});
});
it('resolves Brave classroom web search config without an API key', async () => {
const { resolveClassroomWebSearchConfig } = await import('@/lib/server/web-search-config');
expect(resolveClassroomWebSearchConfig({ webSearchProviderId: 'brave' })).toEqual({
providerId: 'brave',
apiKey: '',
baseUrl: undefined,
});
});
it('resolves MiniMax classroom web search config from dedicated server env vars', async () => {
vi.stubEnv('WEB_SEARCH_MINIMAX_API_KEY', 'minimax-server-key');
vi.stubEnv('WEB_SEARCH_MINIMAX_BASE_URL', 'https://api.minimaxi.com');
const { resolveClassroomWebSearchConfig } = await import('@/lib/server/web-search-config');
expect(resolveClassroomWebSearchConfig({ webSearchProviderId: 'minimax' })).toEqual({
providerId: 'minimax',
apiKey: 'minimax-server-key',
baseUrl: 'https://api.minimaxi.com',
});
});
it('allows official Claude client base URLs and rejects others', async () => {
const { resolveSafeClientWebSearchBaseUrl } = await import('@/lib/server/web-search-config');
expect(resolveSafeClientWebSearchBaseUrl('claude', 'https://api.anthropic.com/v1')).toBe(
'https://api.anthropic.com/v1',
);
expect(resolveSafeClientWebSearchBaseUrl('claude', 'https://api.anthropic.com/')).toBe(
'https://api.anthropic.com',
);
expect(() =>
resolveSafeClientWebSearchBaseUrl('claude', 'https://evil.example.com/v1'),
).toThrow('Unsupported Claude base URL');
});
it('resolves Claude classroom web search config with the client-selected model', async () => {
const { resolveClassroomWebSearchConfig } = await import('@/lib/server/web-search-config');
expect(
resolveClassroomWebSearchConfig({
webSearchProviderId: 'claude',
webSearchApiKey: 'sk-client-key',
webSearchModelId: 'claude-opus-5',
}),
).toEqual({
providerId: 'claude',
apiKey: 'sk-client-key',
baseUrl: undefined,
claudeModelId: 'claude-opus-5',
});
});
it('pins the Claude model from WEB_SEARCH_CLAUDE_MODELS over the client model', async () => {
vi.stubEnv('WEB_SEARCH_CLAUDE_API_KEY', 'sk-server-key');
vi.stubEnv('WEB_SEARCH_CLAUDE_BASE_URL', 'https://api.anthropic.com/v1');
vi.stubEnv('WEB_SEARCH_CLAUDE_MODELS', 'claude-sonnet-5,claude-opus-5');
const { resolveClassroomWebSearchConfig } = await import('@/lib/server/web-search-config');
expect(
resolveClassroomWebSearchConfig({
webSearchProviderId: 'claude',
webSearchModelId: 'claude-haiku-4-5',
}),
).toEqual({
providerId: 'claude',
apiKey: 'sk-server-key',
baseUrl: 'https://api.anthropic.com/v1',
claudeModelId: 'claude-sonnet-5',
});
});
it.each([
'http://127.0.0.1:6060',
'http://localhost:6060',
'http://169.254.169.254',
'http://192.168.161.100:6060/search',
])('rejects client-supplied SearXNG base URLs (%s)', async (baseUrl) => {
const { resolveSafeClientWebSearchBaseUrl } = await import('@/lib/server/web-search-config');
expect(() => resolveSafeClientWebSearchBaseUrl('searxng', baseUrl)).toThrow(
'Unsupported SearXNG base URL',
);
});
it('resolves SearXNG classroom web search config from server base URL', async () => {
vi.stubEnv('SEARXNG_BASE_URL', 'http://192.168.161.100:6060');
const { resolveClassroomWebSearchConfig } = await import('@/lib/server/web-search-config');
expect(resolveClassroomWebSearchConfig({ webSearchProviderId: 'searxng' })).toEqual({
providerId: 'searxng',
apiKey: '',
baseUrl: 'http://192.168.161.100:6060',
});
});
it('returns undefined for SearXNG classroom config without a base URL', async () => {
const { resolveClassroomWebSearchConfig } = await import('@/lib/server/web-search-config');
expect(resolveClassroomWebSearchConfig({ webSearchProviderId: 'searxng' })).toBeUndefined();
});
it('keeps Baidu sub-source toggles in classroom web search config', async () => {
vi.stubEnv('BAIDU_API_KEY', 'baidu-server-key');
const { resolveClassroomWebSearchConfig } = await import('@/lib/server/web-search-config');
expect(
resolveClassroomWebSearchConfig({
webSearchProviderId: 'baidu',
baiduSubSources: { webSearch: false, baike: true, scholar: false },
}),
).toEqual({
providerId: 'baidu',
apiKey: 'baidu-server-key',
baseUrl: undefined,
baiduSubSources: { webSearch: false, baike: true, scholar: false },
});
});
});