Skip to content

Commit 52260c1

Browse files
committed
chore(tests): add testing for invalid json raising
1 parent 7c0cb84 commit 52260c1

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import Anthropic from '@anthropic-ai/sdk';
2+
import { AnthropicError } from '@anthropic-ai/sdk/error';
3+
import {
4+
type Fetch,
5+
type RequestInfo,
6+
type RequestInit,
7+
type Response,
8+
} from '@anthropic-ai/sdk/internal/builtin-types';
9+
import { PassThrough } from 'stream';
10+
11+
function mockFetch(): {
12+
fetch: Fetch;
13+
handleRequest: (handle: Fetch) => void;
14+
handleStreamEvents: (events: any[]) => void;
15+
} {
16+
const queue: Promise<typeof fetch>[] = [];
17+
const readResolvers: ((handler: typeof fetch) => void)[] = [];
18+
19+
let index = 0;
20+
21+
async function fetch(req: string | RequestInfo, init?: RequestInit): Promise<Response> {
22+
const idx = index++;
23+
if (!queue[idx]) {
24+
queue.push(new Promise((resolve) => readResolvers.push(resolve)));
25+
}
26+
27+
const handler = await queue[idx]!;
28+
return await handler(req, init);
29+
}
30+
31+
function handleRequest(handler: typeof fetch): void {
32+
if (readResolvers.length) {
33+
const resolver = readResolvers.shift()!;
34+
resolver(handler);
35+
return;
36+
}
37+
queue.push(Promise.resolve(handler));
38+
}
39+
40+
function handleStreamEvents(events: any[]) {
41+
handleRequest(async () => {
42+
const stream = new PassThrough();
43+
(async () => {
44+
for (const event of events) {
45+
stream.write(`event: ${event.type}\n`);
46+
stream.write(`data: ${JSON.stringify(event)}\n\n`);
47+
}
48+
stream.end(`done: [DONE]\n\n`);
49+
})();
50+
return new Response(stream, {
51+
headers: {
52+
'Content-Type': 'text/event-stream',
53+
'Transfer-Encoding': 'chunked',
54+
},
55+
});
56+
});
57+
}
58+
59+
return { fetch: fetch as any, handleRequest, handleStreamEvents };
60+
}
61+
62+
describe('BetaMessageStream handling invalid JSON', () => {
63+
it('handles partial JSON parsing errors in input_json_delta events', async () => {
64+
const { fetch, handleStreamEvents } = mockFetch();
65+
66+
const anthropic = new Anthropic({ apiKey: 'test-key', fetch });
67+
68+
const streamEvents = [
69+
{
70+
type: 'message_start',
71+
message: {
72+
type: 'message',
73+
id: 'msg_test',
74+
role: 'assistant',
75+
content: [],
76+
model: 'claude-3-opus-20240229',
77+
stop_reason: null,
78+
stop_sequence: null,
79+
usage: { output_tokens: 0, input_tokens: 10 },
80+
},
81+
},
82+
{
83+
type: 'content_block_start',
84+
content_block: {
85+
type: 'tool_use',
86+
id: 'toolu_test',
87+
name: 'test_tool',
88+
input: {},
89+
},
90+
index: 0,
91+
},
92+
{
93+
type: 'content_block_delta',
94+
delta: {
95+
type: 'input_json_delta',
96+
partial_json: '{"foo": "bar", "baz": ', // valid JSON but incomplete
97+
},
98+
index: 0,
99+
},
100+
{
101+
type: 'content_block_delta',
102+
delta: {
103+
type: 'input_json_delta',
104+
partial_json: '"qux": "quux"}', // valid JSON but not complete
105+
},
106+
index: 0,
107+
},
108+
{
109+
type: 'content_block_delta',
110+
delta: {
111+
type: 'input_json_delta',
112+
partial_json: 'invalid malformed json with syntax errors}', // Invalid JSON
113+
},
114+
index: 0,
115+
},
116+
{
117+
type: 'content_block_stop',
118+
index: 0,
119+
},
120+
{
121+
type: 'message_delta',
122+
usage: { output_tokens: 5 },
123+
delta: { stop_reason: 'end_turn', stop_sequence: null },
124+
},
125+
{
126+
type: 'message_stop',
127+
},
128+
];
129+
130+
handleStreamEvents(streamEvents);
131+
132+
const stream = anthropic.beta.messages.stream({
133+
max_tokens: 1024,
134+
model: 'claude-3-opus-20240229',
135+
messages: [{ role: 'user', content: 'Use the test tool' }],
136+
});
137+
138+
// Collect errors emitted by the stream
139+
const errors: AnthropicError[] = [];
140+
stream.on('error', (error) => {
141+
errors.push(error);
142+
});
143+
144+
// Process the stream to completion
145+
try {
146+
await stream.done();
147+
} catch (error) {
148+
// Stream processing may throw the error
149+
}
150+
151+
// Verify that an error was caught and handled
152+
expect(errors.length).toBe(1);
153+
expect(errors[0]).toBeInstanceOf(AnthropicError);
154+
expect(errors[0]!.message).toContain('Unable to parse tool parameter JSON from model');
155+
expect(errors[0]!.message).toContain('{"foo": "bar", "baz": "qux": "quux"}');
156+
});
157+
});

0 commit comments

Comments
 (0)