Files
ccs/tests/unit/mcp-manager.test.ts
T

270 lines
9.2 KiB
TypeScript

/**
* Unit tests for MCP Manager module
*
* Tests web search MCP configuration logic without filesystem dependencies.
* These tests focus on the pure logic functions that can be tested without
* modifying the actual config files.
*/
import { describe, it, expect } from 'bun:test';
/**
* Test helper: Simulate hasWebSearch detection logic
* This matches the logic in mcp-manager.ts hasMcpWebSearch()
*/
function detectWebSearchMcp(mcpServers: Record<string, unknown>): boolean {
return Object.keys(mcpServers).some((key) => {
const lowerKey = key.toLowerCase();
return (
lowerKey.includes('web-search') ||
lowerKey.includes('websearch') ||
lowerKey.includes('tavily') ||
lowerKey.includes('brave')
);
});
}
/**
* Test helper: Simulate provider configuration logic
* Returns which providers would be added given config and env
*/
function getProvidersToAdd(
wsConfig: { enabled: boolean; provider: string; fallback: boolean },
hasExistingWebSearch: boolean,
apiKeys: { brave?: string; tavily?: string }
): string[] {
if (!wsConfig.enabled) return [];
if (hasExistingWebSearch) return [];
const providers: string[] = [];
if (wsConfig.provider === 'auto') {
providers.push('web-search-prime');
if (wsConfig.fallback) {
if (apiKeys.brave) providers.push('brave-search');
if (apiKeys.tavily) providers.push('tavily');
}
} else if (wsConfig.provider === 'web-search-prime') {
providers.push('web-search-prime');
} else if (wsConfig.provider === 'brave') {
if (apiKeys.brave) {
providers.push('brave-search');
} else if (wsConfig.fallback) {
// Fallback chain
providers.push('web-search-prime');
if (apiKeys.tavily) providers.push('tavily');
}
} else if (wsConfig.provider === 'tavily') {
if (apiKeys.tavily) {
providers.push('tavily');
} else if (wsConfig.fallback) {
// Fallback chain
providers.push('web-search-prime');
if (apiKeys.brave) providers.push('brave-search');
}
}
return providers;
}
describe('mcp-manager logic', () => {
describe('web search detection', () => {
it('should detect web-search-prime', () => {
const servers = { 'web-search-prime': { type: 'http' } };
expect(detectWebSearchMcp(servers)).toBe(true);
});
it('should detect brave-search', () => {
const servers = { 'brave-search': { type: 'stdio' } };
expect(detectWebSearchMcp(servers)).toBe(true);
});
it('should detect tavily', () => {
const servers = { tavily: { type: 'stdio' } };
expect(detectWebSearchMcp(servers)).toBe(true);
});
it('should be case-insensitive', () => {
expect(detectWebSearchMcp({ 'WebSearch-Custom': {} })).toBe(true);
expect(detectWebSearchMcp({ 'WEB-SEARCH': {} })).toBe(true);
expect(detectWebSearchMcp({ TAVILY: {} })).toBe(true);
expect(detectWebSearchMcp({ 'Brave-Search': {} })).toBe(true);
});
it('should not detect unrelated MCPs', () => {
expect(detectWebSearchMcp({ 'my-custom-mcp': {} })).toBe(false);
expect(detectWebSearchMcp({ filesystem: {} })).toBe(false);
expect(detectWebSearchMcp({ 'github-copilot': {} })).toBe(false);
});
it('should return false for empty servers', () => {
expect(detectWebSearchMcp({})).toBe(false);
});
});
describe('provider selection logic', () => {
const defaultConfig = { enabled: true, provider: 'auto', fallback: true };
const noApiKeys = {};
const braveOnly = { brave: 'test-key' };
const tavilyOnly = { tavily: 'test-key' };
const bothKeys = { brave: 'brave-key', tavily: 'tavily-key' };
it('should add web-search-prime in auto mode', () => {
const providers = getProvidersToAdd(defaultConfig, false, noApiKeys);
expect(providers).toContain('web-search-prime');
});
it('should add brave-search when API key available in auto mode', () => {
const providers = getProvidersToAdd(defaultConfig, false, braveOnly);
expect(providers).toContain('web-search-prime');
expect(providers).toContain('brave-search');
});
it('should add tavily when API key available in auto mode', () => {
const providers = getProvidersToAdd(defaultConfig, false, tavilyOnly);
expect(providers).toContain('web-search-prime');
expect(providers).toContain('tavily');
});
it('should add all providers when both API keys available', () => {
const providers = getProvidersToAdd(defaultConfig, false, bothKeys);
expect(providers).toContain('web-search-prime');
expect(providers).toContain('brave-search');
expect(providers).toContain('tavily');
});
it('should not add fallbacks when fallback=false', () => {
const config = { enabled: true, provider: 'auto', fallback: false };
const providers = getProvidersToAdd(config, false, bothKeys);
expect(providers).toEqual(['web-search-prime']);
});
it('should skip when disabled', () => {
const config = { enabled: false, provider: 'auto', fallback: true };
const providers = getProvidersToAdd(config, false, bothKeys);
expect(providers).toEqual([]);
});
it('should skip when web search already exists', () => {
const providers = getProvidersToAdd(defaultConfig, true, bothKeys);
expect(providers).toEqual([]);
});
it('should use specific provider when configured', () => {
const config = { enabled: true, provider: 'brave', fallback: false };
const providers = getProvidersToAdd(config, false, braveOnly);
expect(providers).toEqual(['brave-search']);
});
it('should fallback when specific provider not available', () => {
const config = { enabled: true, provider: 'tavily', fallback: true };
// No tavily key, should fallback
const providers = getProvidersToAdd(config, false, braveOnly);
expect(providers).toContain('web-search-prime');
expect(providers).toContain('brave-search');
expect(providers).not.toContain('tavily');
});
it('should return empty when provider unavailable and fallback=false', () => {
const config = { enabled: true, provider: 'brave', fallback: false };
// No brave key and no fallback
const providers = getProvidersToAdd(config, false, noApiKeys);
expect(providers).toEqual([]);
});
});
describe('MCP server config structures', () => {
it('should define correct web-search-prime structure', () => {
const webSearchPrimeConfig = {
type: 'http',
url: 'https://api.z.ai/api/mcp/web_search_prime/mcp',
headers: {},
};
expect(webSearchPrimeConfig.type).toBe('http');
expect(webSearchPrimeConfig.url).toContain('web_search_prime');
});
it('should define correct brave-search structure', () => {
const braveConfig = {
type: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-brave-search'],
env: { BRAVE_API_KEY: 'test-key' },
};
expect(braveConfig.type).toBe('stdio');
expect(braveConfig.command).toBe('npx');
expect(braveConfig.args).toContain('@modelcontextprotocol/server-brave-search');
expect(braveConfig.env.BRAVE_API_KEY).toBe('test-key');
});
it('should define correct tavily structure', () => {
const tavilyConfig = {
type: 'stdio',
command: 'npx',
args: ['-y', '@tavily/mcp-server'],
env: { TAVILY_API_KEY: 'test-key' },
};
expect(tavilyConfig.type).toBe('stdio');
expect(tavilyConfig.args).toContain('@tavily/mcp-server');
expect(tavilyConfig.env.TAVILY_API_KEY).toBe('test-key');
});
});
describe('hook configuration', () => {
it('should define correct PreToolUse hook structure', () => {
const hookConfig = {
PreToolUse: [
{
matcher: 'WebSearch',
hooks: [
{
type: 'command',
command: 'node "/path/to/hook.cjs"',
timeout: 5,
},
],
},
],
};
expect(hookConfig.PreToolUse).toBeDefined();
expect(hookConfig.PreToolUse[0].matcher).toBe('WebSearch');
expect(hookConfig.PreToolUse[0].hooks[0].type).toBe('command');
expect(hookConfig.PreToolUse[0].hooks[0].timeout).toBe(5);
});
});
describe('MCP config file path', () => {
it('should be located in .claude directory', () => {
// The MCP config path should follow this pattern
const expectedPathPattern = '.claude/.mcp.json';
const testPath = '/home/user/.claude/.mcp.json';
expect(testPath).toContain(expectedPathPattern);
});
});
describe('WebSearch config defaults', () => {
it('should have correct default values', () => {
const defaults = {
enabled: true,
provider: 'auto',
fallback: true,
};
expect(defaults.enabled).toBe(true);
expect(defaults.provider).toBe('auto');
expect(defaults.fallback).toBe(true);
});
it('should validate provider options', () => {
const validProviders = ['auto', 'web-search-prime', 'brave', 'tavily'];
expect(validProviders).toContain('auto');
expect(validProviders).toContain('web-search-prime');
expect(validProviders).toContain('brave');
expect(validProviders).toContain('tavily');
});
});
});