Skip to content

Commit f149c5b

Browse files
authored
test: add more chat completions cases (#97)
1 parent 5556281 commit f149c5b

7 files changed

Lines changed: 1328 additions & 39 deletions

File tree

tests/fixtures/mock-upstream.ts

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,16 @@ export interface RecordedRequest {
1010
bodyJson: unknown;
1111
}
1212

13-
export type OpenAiMockStreamEvent = Record<string, unknown> | '[DONE]';
13+
export type OpenAiMockStreamData = Record<string, unknown> | string;
14+
15+
export interface OpenAiMockSseFrame {
16+
data: OpenAiMockStreamData;
17+
event?: string;
18+
delayMs?: number;
19+
disconnectAfterWrite?: boolean;
20+
}
21+
22+
export type OpenAiMockStreamEvent = OpenAiMockStreamData | OpenAiMockSseFrame;
1423

1524
export interface OpenAiMockUpstreamOptions {
1625
model?: string;
@@ -48,6 +57,25 @@ const readBody = async (req: NodeJS.ReadableStream) => {
4857
return Buffer.concat(chunks).toString('utf8');
4958
};
5059

60+
const isSseFrame = (
61+
event: OpenAiMockStreamEvent,
62+
): event is OpenAiMockSseFrame =>
63+
typeof event === 'object' && event !== null && 'data' in event;
64+
65+
const renderSseFrame = (frame: OpenAiMockSseFrame) => {
66+
const lines: string[] = [];
67+
68+
if (frame.event) {
69+
lines.push(`event: ${frame.event}`);
70+
}
71+
72+
const payload =
73+
typeof frame.data === 'string' ? frame.data : JSON.stringify(frame.data);
74+
lines.push(`data: ${payload}`);
75+
76+
return `${lines.join('\n')}\n\n`;
77+
};
78+
5179
const defaultNonStreamBody = (model: string) => ({
5280
id: 'chatcmpl-e2e-mock',
5381
object: 'chat.completion',
@@ -125,6 +153,63 @@ const defaultStreamEvents = (model: string) => [
125153
'[DONE]' as const,
126154
];
127155

156+
export const buildOpenAiTrailingContentAfterFinishReasonStreamEvents = (
157+
model: string,
158+
): OpenAiMockStreamEvent[] => [
159+
{
160+
id: 'chatcmpl-late-delta-e2e-mock',
161+
object: 'chat.completion.chunk',
162+
created: 1,
163+
model,
164+
choices: [
165+
{
166+
index: 0,
167+
delta: { role: 'assistant', content: 'hello ' },
168+
finish_reason: null,
169+
},
170+
],
171+
},
172+
{
173+
id: 'chatcmpl-late-delta-e2e-mock',
174+
object: 'chat.completion.chunk',
175+
created: 1,
176+
model,
177+
choices: [
178+
{
179+
index: 0,
180+
delta: {},
181+
finish_reason: 'stop',
182+
},
183+
],
184+
},
185+
{
186+
id: 'chatcmpl-late-delta-e2e-mock',
187+
object: 'chat.completion.chunk',
188+
created: 1,
189+
model,
190+
choices: [
191+
{
192+
index: 0,
193+
delta: { content: 'from trailing delta' },
194+
finish_reason: null,
195+
},
196+
],
197+
},
198+
{
199+
id: 'chatcmpl-late-delta-e2e-mock',
200+
object: 'chat.completion.chunk',
201+
created: 1,
202+
model,
203+
choices: [],
204+
usage: {
205+
prompt_tokens: 10,
206+
completion_tokens: 8,
207+
total_tokens: 18,
208+
},
209+
},
210+
'[DONE]' as const,
211+
];
212+
128213
export const buildOpenAiToolCallStreamEvents = (
129214
model: string,
130215
): OpenAiMockStreamEvent[] => [
@@ -474,14 +559,20 @@ export const startOpenAiMockUpstream = async (
474559

475560
let sentEvents = 0;
476561
for (const event of current.streamEvents ?? defaultStreamEvents(model)) {
477-
if (typeof event === 'string') {
478-
res.write(`data: ${event}\n\n`);
479-
} else {
480-
res.write(`data: ${JSON.stringify(event)}\n\n`);
481-
}
562+
const frame: OpenAiMockSseFrame = isSseFrame(event)
563+
? event
564+
: { data: event };
565+
566+
res.write(renderSseFrame(frame));
482567

483568
sentEvents += 1;
484569

570+
if (frame.disconnectAfterWrite) {
571+
await new Promise((resolve) => setImmediate(resolve));
572+
res.socket?.destroy();
573+
return;
574+
}
575+
485576
if (
486577
current.disconnectAfterEvents !== undefined &&
487578
sentEvents >= current.disconnectAfterEvents
@@ -491,8 +582,9 @@ export const startOpenAiMockUpstream = async (
491582
return;
492583
}
493584

494-
if (current.eventDelayMs) {
495-
await sleep(current.eventDelayMs);
585+
const delayMs = frame.delayMs ?? current.eventDelayMs;
586+
if (delayMs) {
587+
await sleep(delayMs);
496588
}
497589
}
498590

tests/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"test": "vitest run",
99
"test:dev": "vitest"
1010
},
11-
"packageManager": "pnpm@11.0.8+sha512.4c4097e1dd2d42372c4e7fa5a791ff28fc75a484c7ac192e64b1df0fdef17594ba982f9b4fed9adfb3c757846f565b799b2763fb3733d1de1bcb82cf46684912",
11+
"packageManager": "pnpm@11.0.9+sha512.34ce82e6780233cf9cad8685029a8f81d2e06196c5a9bad98879f7424940c6817c4e4524fb7d38b8553ceed48b9758b8ebaf1abd3600c232c4c8cf7366086f38",
1212
"devDependencies": {
1313
"@anthropic-ai/sdk": "^0.88.0",
1414
"@eslint/js": "^10.0.1",

0 commit comments

Comments
 (0)