Skip to content

Commit abed76c

Browse files
apartsinclaude
andcommitted
test: extend test suite with 208 new tests covering blind spots
New Python tests (134 tests across 6 files): - test_middleware_contrib.py: Correlation ID + OpenTelemetry middleware - test_enhanced_mock.py: Error simulation, chaos testing, backward compat - test_observability_connectors.py: Webhook, JSON log, callback connectors - test_budget_edge_cases.py: Budget enforcement, cost tracking edge cases - test_langchain_adapter.py: LangChain adapter message conversion + invoke - test_error_edge_cases.py: Exception hierarchy, retryability, recovery New TypeScript tests (74 tests across 4 files): - mixins.test.ts: Circuit breaker, retry, cache, rate limiter - prometheus.test.ts: Prometheus metrics connector - middleware-contrib.test.ts: Correlation ID + OpenTelemetry middleware - enhanced-mock.test.ts: Enhanced MockClient features Total: 2,101 tests (1,314 Python + 787 TypeScript), up from 1,893. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9ca4da8 commit abed76c

10 files changed

+2980
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/**
2+
* Tests for the enhanced MockClient and MockResponse testing utilities.
3+
*/
4+
import { MockClient, MockResponse, mockClient } from '@/testing';
5+
6+
// ---------------------------------------------------------------------------
7+
// Enhanced MockClient
8+
// ---------------------------------------------------------------------------
9+
10+
describe('Enhanced MockClient', () => {
11+
test('MockResponse with error throws', async () => {
12+
const client = mockClient({
13+
responses: [
14+
{ error: new Error('API rate limit exceeded') },
15+
],
16+
});
17+
18+
await expect(
19+
client.chat.completions.create({
20+
model: 'test-pool',
21+
messages: [{ role: 'user', content: 'Hi' }],
22+
})
23+
).rejects.toThrow('API rate limit exceeded');
24+
});
25+
26+
test('MockResponse with delay adds latency', async () => {
27+
jest.useFakeTimers();
28+
29+
const client = mockClient({
30+
responses: [{ content: 'Delayed response', delay: 1 }],
31+
});
32+
33+
const promise = client.chat.completions.create({
34+
model: 'test-pool',
35+
messages: [{ role: 'user', content: 'Hi' }],
36+
});
37+
38+
// Advance past the delay
39+
jest.advanceTimersByTime(1100);
40+
41+
const resp = await promise;
42+
expect(resp.choices[0].message?.content).toBe('Delayed response');
43+
44+
jest.useRealTimers();
45+
});
46+
47+
test('error after success sequence works', async () => {
48+
const client = mockClient({
49+
responses: [
50+
{ content: 'First OK' },
51+
{ error: new Error('Second fails') },
52+
],
53+
});
54+
55+
// First call succeeds
56+
const resp1 = await client.chat.completions.create({
57+
model: 'test-pool',
58+
messages: [{ role: 'user', content: 'Hello' }],
59+
});
60+
expect(resp1.choices[0].message?.content).toBe('First OK');
61+
62+
// Second call throws
63+
await expect(
64+
client.chat.completions.create({
65+
model: 'test-pool',
66+
messages: [{ role: 'user', content: 'Again' }],
67+
})
68+
).rejects.toThrow('Second fails');
69+
});
70+
71+
test('error with delay is delayed', async () => {
72+
jest.useFakeTimers();
73+
74+
const client = mockClient({
75+
responses: [{ error: new Error('Timeout error'), delay: 2 }],
76+
});
77+
78+
const promise = client.chat.completions.create({
79+
model: 'test-pool',
80+
messages: [{ role: 'user', content: 'Hi' }],
81+
});
82+
83+
jest.advanceTimersByTime(2100);
84+
85+
await expect(promise).rejects.toThrow('Timeout error');
86+
87+
jest.useRealTimers();
88+
});
89+
90+
test('failureRate=0 never fails', async () => {
91+
const client = mockClient({
92+
responses: [{ content: 'OK' }],
93+
failureRate: 0,
94+
});
95+
96+
// Run multiple calls -- none should fail from chaos
97+
for (let i = 0; i < 20; i++) {
98+
const resp = await client.chat.completions.create({
99+
model: 'test-pool',
100+
messages: [{ role: 'user', content: 'Hi' }],
101+
});
102+
expect(resp.choices[0].message?.content).toBe('OK');
103+
}
104+
});
105+
106+
test('failureRate=1 always fails', async () => {
107+
const client = mockClient({
108+
responses: [{ content: 'Should not see this' }],
109+
failureRate: 1.0,
110+
});
111+
112+
await expect(
113+
client.chat.completions.create({
114+
model: 'test-pool',
115+
messages: [{ role: 'user', content: 'Hi' }],
116+
})
117+
).rejects.toThrow('Simulated random failure');
118+
});
119+
120+
// -- Backward compatibility ------------------------------------------------
121+
122+
test('basic response still works', async () => {
123+
const client = mockClient({
124+
responses: [{ content: 'Hello from mock' }],
125+
});
126+
127+
const resp = await client.chat.completions.create({
128+
model: 'test-pool',
129+
messages: [{ role: 'user', content: 'Hi' }],
130+
});
131+
132+
expect(resp.choices[0].message?.content).toBe('Hello from mock');
133+
expect(resp.object).toBe('chat.completion');
134+
expect(client.calls.length).toBe(1);
135+
expect(client.calls[0].model).toBe('test-pool');
136+
});
137+
138+
test('multiple response sequence still works', async () => {
139+
const client = mockClient({
140+
responses: [
141+
{ content: 'First' },
142+
{ content: 'Second' },
143+
{ content: 'Third' },
144+
],
145+
});
146+
147+
const r1 = await client.chat.completions.create({
148+
model: 'test',
149+
messages: [{ role: 'user', content: '1' }],
150+
});
151+
const r2 = await client.chat.completions.create({
152+
model: 'test',
153+
messages: [{ role: 'user', content: '2' }],
154+
});
155+
const r3 = await client.chat.completions.create({
156+
model: 'test',
157+
messages: [{ role: 'user', content: '3' }],
158+
});
159+
160+
expect(r1.choices[0].message?.content).toBe('First');
161+
expect(r2.choices[0].message?.content).toBe('Second');
162+
expect(r3.choices[0].message?.content).toBe('Third');
163+
expect(client.calls.length).toBe(3);
164+
});
165+
166+
test('default MockResponse returns "Mock response"', async () => {
167+
const client = mockClient(); // no responses specified
168+
169+
const resp = await client.chat.completions.create({
170+
model: 'test',
171+
messages: [{ role: 'user', content: 'Hi' }],
172+
});
173+
174+
expect(resp.choices[0].message?.content).toBe('Mock response');
175+
});
176+
177+
test('MockResponse with model sets model field', async () => {
178+
const client = mockClient({
179+
responses: [{ content: 'Hello', model: 'claude-3-opus' }],
180+
});
181+
182+
const resp = await client.chat.completions.create({
183+
model: 'test',
184+
messages: [{ role: 'user', content: 'Hi' }],
185+
});
186+
187+
expect(resp.model).toBe('claude-3-opus');
188+
});
189+
190+
test('MockResponse with tokens sets usage', async () => {
191+
const client = mockClient({
192+
responses: [{ content: 'Hello', tokens: 30 }],
193+
});
194+
195+
const resp = await client.chat.completions.create({
196+
model: 'test',
197+
messages: [{ role: 'user', content: 'Hi' }],
198+
});
199+
200+
expect(resp.usage).toBeDefined();
201+
expect(resp.usage!.totalTokens).toBe(30);
202+
expect(resp.usage!.promptTokens).toBe(10); // floor(30/3)
203+
expect(resp.usage!.completionTokens).toBe(20); // 30 - 10
204+
});
205+
206+
test('MockClient provides helper methods', () => {
207+
const client = new MockClient();
208+
209+
// poolStatus
210+
const status = client.poolStatus();
211+
expect(status['mock-pool']).toBeDefined();
212+
213+
// activeProviders
214+
const providers = client.activeProviders();
215+
expect(providers).toContain('mock-provider');
216+
217+
// describe
218+
const desc = client.describe();
219+
expect(desc).toContain('mock-pool');
220+
221+
// explain
222+
const explanation = client.explain();
223+
expect(explanation.poolName).toBe('mock-pool');
224+
expect(explanation.strategy).toBe('mock');
225+
226+
// models
227+
const models = client.models.list();
228+
expect(models.object).toBe('list');
229+
230+
// close
231+
expect(() => client.close()).not.toThrow();
232+
});
233+
234+
test('calls record includes kwargs', async () => {
235+
const client = mockClient({
236+
responses: [{ content: 'OK' }],
237+
});
238+
239+
await client.chat.completions.create({
240+
model: 'test',
241+
messages: [{ role: 'user', content: 'Hi' }],
242+
temperature: 0.7,
243+
max_tokens: 100,
244+
});
245+
246+
expect(client.calls[0].kwargs).toEqual({
247+
temperature: 0.7,
248+
max_tokens: 100,
249+
});
250+
});
251+
252+
test('exhausted responses repeat last response', async () => {
253+
const client = mockClient({
254+
responses: [
255+
{ content: 'First' },
256+
{ content: 'Last' },
257+
],
258+
});
259+
260+
await client.chat.completions.create({ model: 'test', messages: [] });
261+
await client.chat.completions.create({ model: 'test', messages: [] });
262+
const r3 = await client.chat.completions.create({ model: 'test', messages: [] });
263+
264+
// After exhausting the list, the last response should repeat
265+
expect(r3.choices[0].message?.content).toBe('Last');
266+
});
267+
});

0 commit comments

Comments
 (0)