client.test.ts
1 /** 2 * Tests for Claude API client 3 */ 4 import { describe, it, expect, beforeEach, vi } from 'vitest'; 5 import { ClaudeClient, ClaudeAPIError, getClaudeClient, resetClaudeClient } from '../../../src/lib/claude/client'; 6 7 describe('ClaudeClient', () => { 8 let client: ClaudeClient; 9 10 beforeEach(() => { 11 client = new ClaudeClient(); 12 }); 13 14 describe('setApiKey', () => { 15 it('sets the API key', () => { 16 client.setApiKey('sk-test-key'); 17 expect(client.isConfigured()).toBe(true); 18 }); 19 20 it('allows null to clear the key', () => { 21 client.setApiKey('sk-test-key'); 22 client.setApiKey(null); 23 expect(client.isConfigured()).toBe(false); 24 }); 25 }); 26 27 describe('isConfigured', () => { 28 it('returns false when no key is set', () => { 29 expect(client.isConfigured()).toBe(false); 30 }); 31 32 it('returns true when key is set', () => { 33 client.setApiKey('sk-test-key'); 34 expect(client.isConfigured()).toBe(true); 35 }); 36 37 it('returns false for empty string', () => { 38 client.setApiKey(''); 39 expect(client.isConfigured()).toBe(false); 40 }); 41 }); 42 43 describe('getApiKeyMasked', () => { 44 it('returns null when no key is set', () => { 45 expect(client.getApiKeyMasked()).toBe(null); 46 }); 47 48 it('masks long API keys', () => { 49 client.setApiKey('sk-ant-api03-very-long-key-here'); 50 const masked = client.getApiKeyMasked(); 51 expect(masked).toMatch(/^sk-a\.\.\.here$/); 52 }); 53 54 it('returns **** for short keys', () => { 55 client.setApiKey('short'); 56 expect(client.getApiKeyMasked()).toBe('****'); 57 }); 58 }); 59 60 describe('setModel', () => { 61 it('sets the model', () => { 62 // No direct way to test this without making a request 63 // Just verify it doesn't throw 64 expect(() => client.setModel('claude-3-opus')).not.toThrow(); 65 }); 66 }); 67 68 describe('sendMessage', () => { 69 it('throws error when not configured', async () => { 70 await expect( 71 client.sendMessage([{ role: 'user', content: 'test' }]) 72 ).rejects.toThrow('Claude API key not configured'); 73 }); 74 }); 75 }); 76 77 describe('ClaudeAPIError', () => { 78 it('creates error with message only', () => { 79 const error = new ClaudeAPIError('Test error'); 80 expect(error.message).toBe('Test error'); 81 expect(error.name).toBe('ClaudeAPIError'); 82 expect(error.statusCode).toBeUndefined(); 83 expect(error.errorType).toBeUndefined(); 84 }); 85 86 it('creates error with status code', () => { 87 const error = new ClaudeAPIError('Test error', 401); 88 expect(error.statusCode).toBe(401); 89 }); 90 91 it('creates error with error type', () => { 92 const error = new ClaudeAPIError('Test error', 400, 'invalid_request'); 93 expect(error.errorType).toBe('invalid_request'); 94 }); 95 }); 96 97 describe('getClaudeClient', () => { 98 beforeEach(() => { 99 resetClaudeClient(); 100 }); 101 102 it('returns a ClaudeClient instance', () => { 103 const client = getClaudeClient(); 104 expect(client).toBeInstanceOf(ClaudeClient); 105 }); 106 107 it('returns the same instance on subsequent calls', () => { 108 const client1 = getClaudeClient(); 109 const client2 = getClaudeClient(); 110 expect(client1).toBe(client2); 111 }); 112 }); 113 114 describe('resetClaudeClient', () => { 115 it('creates a new instance after reset', () => { 116 const client1 = getClaudeClient(); 117 client1.setApiKey('test-key'); 118 119 resetClaudeClient(); 120 121 const client2 = getClaudeClient(); 122 expect(client2.isConfigured()).toBe(false); 123 }); 124 });