/** * Tests for Claude Provider */ import { describe, it, expect, beforeEach, jest } from 'bun:test'; import { ClaudeProvider, createClaudeProvider, createResponseType, parseAndValidateResponseType, AIProviderError, AIErrorType } from '../src/index.js'; describe('ClaudeProvider', () => { let provider: ClaudeProvider; beforeEach(() => { provider = new ClaudeProvider({ apiKey: 'test-api-key', defaultModel: 'claude-3-5-haiku-20241022' }); }); describe('constructor', () => { it('should create provider with valid config', () => { expect(provider).toBeInstanceOf(ClaudeProvider); expect(provider.isInitialized()).toBe(false); }); it('should throw error for missing API key', () => { expect(() => { new ClaudeProvider({ apiKey: '' }); }).toThrow(AIProviderError); }); }); describe('getInfo', () => { it('should return provider information', () => { const info = provider.getInfo(); expect(info.name).toBe('Claude'); expect(info.version).toBe('1.0.0'); expect(info.supportsStreaming).toBe(true); expect(info.models).toContain('claude-3-5-sonnet-20241022'); expect(info.maxContextLength).toBe(200000); }); }); describe('validation', () => { it('should validate temperature range', async () => { // Mock initialization to avoid API call (provider as any).initialized = true; (provider as any).client = {}; await expect( provider.complete({ messages: [{ role: 'user', content: 'test' }], temperature: 1.5 }) ).rejects.toThrow('Temperature must be a number between 0.0 and 1.0'); }); it('should validate message format', async () => { (provider as any).initialized = true; (provider as any).client = {}; await expect( provider.complete({ messages: [{ role: 'invalid' as any, content: 'test' }] }) ).rejects.toThrow('Message at index 0 has invalid role \'invalid\'. Must be: system, user, assistant'); }); it('should require initialization before use', async () => { await expect( provider.complete({ messages: [{ role: 'user', content: 'test' }] }) ).rejects.toThrow('Provider must be initialized before use'); }); }); describe('error handling', () => { it('should handle authentication errors', () => { const error = new Error('Unauthorized'); (error as any).status = 401; const providerError = (provider as any).handleAnthropicError(error); expect(providerError).toBeInstanceOf(AIProviderError); expect(providerError.type).toBe(AIErrorType.AUTHENTICATION); }); it('should handle rate limit errors', () => { const error = new Error('Rate limited'); (error as any).status = 429; const providerError = (provider as any).handleAnthropicError(error); expect(providerError).toBeInstanceOf(AIProviderError); expect(providerError.type).toBe(AIErrorType.RATE_LIMIT); }); it('should handle model not found errors', () => { const error = new Error('Model not found'); (error as any).status = 404; const providerError = (provider as any).handleAnthropicError(error); expect(providerError).toBeInstanceOf(AIProviderError); expect(providerError.type).toBe(AIErrorType.MODEL_NOT_FOUND); }); }); describe('message conversion', () => { it('should separate system messages from conversation', () => { const messages = [ { role: 'system' as const, content: 'You are helpful' }, { role: 'user' as const, content: 'Hello' }, { role: 'assistant' as const, content: 'Hi there' }, { role: 'system' as const, content: 'Be concise' } ]; const result = (provider as any).processMessages(messages); expect(result.system).toBe('You are helpful\n\nBe concise'); expect(result.messages).toHaveLength(2); expect(result.messages[0].role).toBe('user'); expect(result.messages[1].role).toBe('assistant'); }); it('should handle messages without system prompts', () => { const messages = [ { role: 'user' as const, content: 'Hello' }, { role: 'assistant' as const, content: 'Hi there' } ]; const result = (provider as any).processMessages(messages); expect(result.system).toBeNull(); expect(result.messages).toHaveLength(2); }); }); describe('structured responses', () => { it('should handle structured responses correctly', async () => { const provider = new ClaudeProvider({ apiKey: 'test-key' }); const mockResponse = { content: JSON.stringify({ name: 'John Doe', age: 30 }), model: 'claude-3-5-sonnet-20241022', usage: { promptTokens: 10, completionTokens: 20, totalTokens: 30 }, id: 'test-id' }; (provider as any).doComplete = jest.fn().mockResolvedValue(mockResponse); (provider as any).initialized = true; const responseType = createResponseType<{ name: string; age: number }>( `{ name: string; age: number }`, 'A user profile' ); const response = await provider.complete<{ name: string; age: number }>({ messages: [{ role: 'user', content: 'test' }], responseType }); expect(response.content).toEqual({ name: 'John Doe', age: 30 }); expect(response.rawContent).toBe(JSON.stringify({ name: 'John Doe', age: 30 })); expect((provider as any).doComplete).toHaveBeenCalled(); }); }); });