Skip to content

Commit 5fbfe9b

Browse files
committed
Add test coverage
1 parent 537514d commit 5fbfe9b

File tree

19 files changed

+3127
-514
lines changed

19 files changed

+3127
-514
lines changed

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
out
22
dist
33
node_modules
4-
.vscode-test/
4+
coverage/
55

66
.DS_Store
77

@@ -13,4 +13,5 @@ roo-cline-*.vsix
1313
/local-prompts
1414

1515
# Test environment
16-
.test_env
16+
.test_env
17+
.vscode-test/
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { AnthropicHandler } from '../anthropic';
2+
import { ApiHandlerOptions } from '../../../shared/api';
3+
import { ApiStream } from '../../transform/stream';
4+
import { Anthropic } from '@anthropic-ai/sdk';
5+
6+
// Mock Anthropic client
7+
const mockBetaCreate = jest.fn();
8+
const mockCreate = jest.fn();
9+
jest.mock('@anthropic-ai/sdk', () => {
10+
return {
11+
Anthropic: jest.fn().mockImplementation(() => ({
12+
beta: {
13+
promptCaching: {
14+
messages: {
15+
create: mockBetaCreate.mockImplementation(async () => ({
16+
async *[Symbol.asyncIterator]() {
17+
yield {
18+
type: 'message_start',
19+
message: {
20+
usage: {
21+
input_tokens: 100,
22+
output_tokens: 50,
23+
cache_creation_input_tokens: 20,
24+
cache_read_input_tokens: 10
25+
}
26+
}
27+
};
28+
yield {
29+
type: 'content_block_start',
30+
index: 0,
31+
content_block: {
32+
type: 'text',
33+
text: 'Hello'
34+
}
35+
};
36+
yield {
37+
type: 'content_block_delta',
38+
delta: {
39+
type: 'text_delta',
40+
text: ' world'
41+
}
42+
};
43+
}
44+
}))
45+
}
46+
}
47+
},
48+
messages: {
49+
create: mockCreate
50+
}
51+
}))
52+
};
53+
});
54+
55+
describe('AnthropicHandler', () => {
56+
let handler: AnthropicHandler;
57+
let mockOptions: ApiHandlerOptions;
58+
59+
beforeEach(() => {
60+
mockOptions = {
61+
apiKey: 'test-api-key',
62+
apiModelId: 'claude-3-5-sonnet-20241022'
63+
};
64+
handler = new AnthropicHandler(mockOptions);
65+
mockBetaCreate.mockClear();
66+
mockCreate.mockClear();
67+
});
68+
69+
describe('constructor', () => {
70+
it('should initialize with provided options', () => {
71+
expect(handler).toBeInstanceOf(AnthropicHandler);
72+
expect(handler.getModel().id).toBe(mockOptions.apiModelId);
73+
});
74+
75+
it('should initialize with undefined API key', () => {
76+
// The SDK will handle API key validation, so we just verify it initializes
77+
const handlerWithoutKey = new AnthropicHandler({
78+
...mockOptions,
79+
apiKey: undefined
80+
});
81+
expect(handlerWithoutKey).toBeInstanceOf(AnthropicHandler);
82+
});
83+
84+
it('should use custom base URL if provided', () => {
85+
const customBaseUrl = 'https://custom.anthropic.com';
86+
const handlerWithCustomUrl = new AnthropicHandler({
87+
...mockOptions,
88+
anthropicBaseUrl: customBaseUrl
89+
});
90+
expect(handlerWithCustomUrl).toBeInstanceOf(AnthropicHandler);
91+
});
92+
});
93+
94+
describe('createMessage', () => {
95+
const systemPrompt = 'You are a helpful assistant.';
96+
const messages: Anthropic.Messages.MessageParam[] = [
97+
{
98+
role: 'user',
99+
content: [{
100+
type: 'text' as const,
101+
text: 'Hello!'
102+
}]
103+
}
104+
];
105+
106+
it('should handle prompt caching for supported models', async () => {
107+
const stream = handler.createMessage(systemPrompt, [
108+
{
109+
role: 'user',
110+
content: [{ type: 'text' as const, text: 'First message' }]
111+
},
112+
{
113+
role: 'assistant',
114+
content: [{ type: 'text' as const, text: 'Response' }]
115+
},
116+
{
117+
role: 'user',
118+
content: [{ type: 'text' as const, text: 'Second message' }]
119+
}
120+
]);
121+
122+
const chunks: any[] = [];
123+
for await (const chunk of stream) {
124+
chunks.push(chunk);
125+
}
126+
127+
// Verify usage information
128+
const usageChunk = chunks.find(chunk => chunk.type === 'usage');
129+
expect(usageChunk).toBeDefined();
130+
expect(usageChunk?.inputTokens).toBe(100);
131+
expect(usageChunk?.outputTokens).toBe(50);
132+
expect(usageChunk?.cacheWriteTokens).toBe(20);
133+
expect(usageChunk?.cacheReadTokens).toBe(10);
134+
135+
// Verify text content
136+
const textChunks = chunks.filter(chunk => chunk.type === 'text');
137+
expect(textChunks).toHaveLength(2);
138+
expect(textChunks[0].text).toBe('Hello');
139+
expect(textChunks[1].text).toBe(' world');
140+
141+
// Verify beta API was used
142+
expect(mockBetaCreate).toHaveBeenCalled();
143+
expect(mockCreate).not.toHaveBeenCalled();
144+
});
145+
});
146+
147+
describe('getModel', () => {
148+
it('should return default model if no model ID is provided', () => {
149+
const handlerWithoutModel = new AnthropicHandler({
150+
...mockOptions,
151+
apiModelId: undefined
152+
});
153+
const model = handlerWithoutModel.getModel();
154+
expect(model.id).toBeDefined();
155+
expect(model.info).toBeDefined();
156+
});
157+
158+
it('should return specified model if valid model ID is provided', () => {
159+
const model = handler.getModel();
160+
expect(model.id).toBe(mockOptions.apiModelId);
161+
expect(model.info).toBeDefined();
162+
expect(model.info.maxTokens).toBe(8192);
163+
expect(model.info.contextWindow).toBe(200_000);
164+
expect(model.info.supportsImages).toBe(true);
165+
expect(model.info.supportsPromptCache).toBe(true);
166+
});
167+
});
168+
});

0 commit comments

Comments
 (0)