feat: implement v2 3-tier architecture (mcpctl → mcplocal → mcpd)
- Rename local-proxy to mcplocal with HTTP server, LLM pipeline, mcpd discovery - Add LLM pre-processing: token estimation, filter cache, metrics, Gemini CLI + DeepSeek providers - Add mcpd auth (login/logout) and MCP proxy endpoints - Update CLI: dual URLs (mcplocalUrl/mcpdUrl), auth commands, --direct flag - Add tiered health monitoring, shell completions, e2e integration tests - 57 test files, 597 tests passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
118
src/mcplocal/tests/providers.test.ts
Normal file
118
src/mcplocal/tests/providers.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ProviderRegistry } from '../src/providers/registry.js';
|
||||
import type { LlmProvider, CompletionOptions, CompletionResult } from '../src/providers/types.js';
|
||||
|
||||
function mockProvider(name: string): LlmProvider {
|
||||
return {
|
||||
name,
|
||||
complete: vi.fn(async (): Promise<CompletionResult> => ({
|
||||
content: `Response from ${name}`,
|
||||
toolCalls: [],
|
||||
usage: { promptTokens: 10, completionTokens: 20, totalTokens: 30 },
|
||||
finishReason: 'stop',
|
||||
})),
|
||||
listModels: vi.fn(async () => [`${name}-model-1`, `${name}-model-2`]),
|
||||
isAvailable: vi.fn(async () => true),
|
||||
};
|
||||
}
|
||||
|
||||
describe('ProviderRegistry', () => {
|
||||
let registry: ProviderRegistry;
|
||||
|
||||
beforeEach(() => {
|
||||
registry = new ProviderRegistry();
|
||||
});
|
||||
|
||||
it('starts with no providers', () => {
|
||||
expect(registry.list()).toEqual([]);
|
||||
expect(registry.getActive()).toBeNull();
|
||||
expect(registry.getActiveName()).toBeNull();
|
||||
});
|
||||
|
||||
it('registers a provider and sets it as active', () => {
|
||||
const openai = mockProvider('openai');
|
||||
registry.register(openai);
|
||||
|
||||
expect(registry.list()).toEqual(['openai']);
|
||||
expect(registry.getActive()).toBe(openai);
|
||||
expect(registry.getActiveName()).toBe('openai');
|
||||
});
|
||||
|
||||
it('first registered provider becomes active', () => {
|
||||
registry.register(mockProvider('openai'));
|
||||
registry.register(mockProvider('anthropic'));
|
||||
|
||||
expect(registry.getActiveName()).toBe('openai');
|
||||
expect(registry.list()).toEqual(['openai', 'anthropic']);
|
||||
});
|
||||
|
||||
it('switches active provider', () => {
|
||||
registry.register(mockProvider('openai'));
|
||||
registry.register(mockProvider('anthropic'));
|
||||
|
||||
registry.setActive('anthropic');
|
||||
expect(registry.getActiveName()).toBe('anthropic');
|
||||
});
|
||||
|
||||
it('throws when setting unknown provider as active', () => {
|
||||
expect(() => registry.setActive('unknown')).toThrow("Provider 'unknown' is not registered");
|
||||
});
|
||||
|
||||
it('gets provider by name', () => {
|
||||
const openai = mockProvider('openai');
|
||||
registry.register(openai);
|
||||
|
||||
expect(registry.get('openai')).toBe(openai);
|
||||
expect(registry.get('unknown')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('unregisters a provider', () => {
|
||||
registry.register(mockProvider('openai'));
|
||||
registry.register(mockProvider('anthropic'));
|
||||
|
||||
registry.unregister('openai');
|
||||
expect(registry.list()).toEqual(['anthropic']);
|
||||
// Active should switch to remaining provider
|
||||
expect(registry.getActiveName()).toBe('anthropic');
|
||||
});
|
||||
|
||||
it('unregistering active provider switches to next available', () => {
|
||||
registry.register(mockProvider('openai'));
|
||||
registry.register(mockProvider('anthropic'));
|
||||
registry.setActive('openai');
|
||||
|
||||
registry.unregister('openai');
|
||||
expect(registry.getActiveName()).toBe('anthropic');
|
||||
});
|
||||
|
||||
it('unregistering last provider clears active', () => {
|
||||
registry.register(mockProvider('openai'));
|
||||
registry.unregister('openai');
|
||||
|
||||
expect(registry.getActive()).toBeNull();
|
||||
expect(registry.getActiveName()).toBeNull();
|
||||
});
|
||||
|
||||
it('active provider can complete', async () => {
|
||||
const provider = mockProvider('openai');
|
||||
registry.register(provider);
|
||||
|
||||
const active = registry.getActive()!;
|
||||
const result = await active.complete({
|
||||
messages: [{ role: 'user', content: 'Hello' }],
|
||||
});
|
||||
|
||||
expect(result.content).toBe('Response from openai');
|
||||
expect(result.finishReason).toBe('stop');
|
||||
expect(provider.complete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('active provider can list models', async () => {
|
||||
registry.register(mockProvider('anthropic'));
|
||||
|
||||
const active = registry.getActive()!;
|
||||
const models = await active.listModels();
|
||||
|
||||
expect(models).toEqual(['anthropic-model-1', 'anthropic-model-2']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user