Skip to content

Commit 99b3168

Browse files
committed
feat(tests): 添加流工具调用参数的测试用例,覆盖完整、分片和并发调用场景
1 parent d36f824 commit 99b3168

1 file changed

Lines changed: 240 additions & 0 deletions

File tree

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import { afterEach, expect, test, mock } from "bun:test"
2+
3+
function asyncIterableFrom(events: Array<{ data?: string }>) {
4+
return {
5+
[Symbol.asyncIterator]() {
6+
let i = 0
7+
return {
8+
next() {
9+
if (i < events.length)
10+
return Promise.resolve({ value: events[i++], done: false })
11+
return Promise.resolve({ value: undefined, done: true })
12+
},
13+
}
14+
},
15+
}
16+
}
17+
18+
afterEach(() => {
19+
mock.restore()
20+
})
21+
22+
test("[Stream] handles complete tool call parameters in single chunk", async () => {
23+
await mock.module("~/services/copilot/create-chat-completions", () => ({
24+
createChatCompletions: () =>
25+
asyncIterableFrom([
26+
{
27+
data: JSON.stringify({
28+
id: "c1",
29+
choices: [
30+
{
31+
index: 0,
32+
delta: {
33+
tool_calls: [
34+
{
35+
index: 0,
36+
type: "function",
37+
function: {
38+
name: "ReadFile",
39+
arguments: '{"absolute_path": "/path/to/file.txt"}',
40+
},
41+
},
42+
],
43+
},
44+
finish_reason: null,
45+
},
46+
],
47+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
48+
}),
49+
},
50+
{
51+
data: JSON.stringify({
52+
id: "c1",
53+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
54+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
55+
}),
56+
},
57+
{ data: "[DONE]" },
58+
]),
59+
}))
60+
61+
await mock.module("~/lib/rate-limit", () => ({
62+
checkRateLimit: (_: unknown) => {},
63+
}))
64+
const { server } = await import("~/server?stream-complete-params")
65+
const res = await server.request(
66+
"/v1beta/models/gemini-pro:streamGenerateContent",
67+
{
68+
method: "POST",
69+
headers: { "content-type": "application/json" },
70+
body: JSON.stringify({
71+
contents: [{ role: "user", parts: [{ text: "Read the file" }] }],
72+
}),
73+
},
74+
)
75+
76+
expect(res.status).toBe(200)
77+
const body = await res.text()
78+
expect(
79+
body.includes(
80+
'"functionCall":{"name":"ReadFile","args":{"absolute_path":"/path/to/file.txt"}}',
81+
),
82+
).toBe(true)
83+
})
84+
85+
test("[Stream] handles fragmented tool call parameters across multiple chunks", async () => {
86+
await mock.module("~/services/copilot/create-chat-completions", () => ({
87+
createChatCompletions: () =>
88+
asyncIterableFrom([
89+
{
90+
data: JSON.stringify({
91+
id: "c1",
92+
choices: [
93+
{
94+
index: 0,
95+
delta: {
96+
tool_calls: [
97+
{
98+
index: 0,
99+
type: "function",
100+
function: { name: "ReadFile", arguments: '{"absolu' },
101+
},
102+
],
103+
},
104+
finish_reason: null,
105+
},
106+
],
107+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
108+
}),
109+
},
110+
{
111+
data: JSON.stringify({
112+
id: "c1",
113+
choices: [
114+
{
115+
index: 0,
116+
delta: {
117+
tool_calls: [
118+
{
119+
index: 0,
120+
type: "function",
121+
function: { arguments: 'te_path": "/file.txt"}' },
122+
},
123+
],
124+
},
125+
finish_reason: null,
126+
},
127+
],
128+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
129+
}),
130+
},
131+
{
132+
data: JSON.stringify({
133+
id: "c1",
134+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
135+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
136+
}),
137+
},
138+
{ data: "[DONE]" },
139+
]),
140+
}))
141+
142+
await mock.module("~/lib/rate-limit", () => ({
143+
checkRateLimit: (_: unknown) => {},
144+
}))
145+
const { server } = await import("~/server?stream-fragmented-params")
146+
const res = await server.request(
147+
"/v1beta/models/gemini-pro:streamGenerateContent",
148+
{
149+
method: "POST",
150+
headers: { "content-type": "application/json" },
151+
body: JSON.stringify({
152+
contents: [{ role: "user", parts: [{ text: "Read the file" }] }],
153+
}),
154+
},
155+
)
156+
157+
expect(res.status).toBe(200)
158+
const body = await res.text()
159+
expect(
160+
body.includes(
161+
'"functionCall":{"name":"ReadFile","args":{"absolute_path":"/file.txt"}}',
162+
),
163+
).toBe(true)
164+
})
165+
166+
test("[Stream] correctly processes multiple concurrent tool calls", async () => {
167+
await mock.module("~/services/copilot/create-chat-completions", () => ({
168+
createChatCompletions: () =>
169+
asyncIterableFrom([
170+
{
171+
data: JSON.stringify({
172+
id: "c1",
173+
choices: [
174+
{
175+
index: 0,
176+
delta: {
177+
tool_calls: [
178+
{
179+
index: 0,
180+
type: "function",
181+
function: {
182+
name: "ReadFile",
183+
arguments: '{"path": "/read.txt"}',
184+
},
185+
},
186+
{
187+
index: 1,
188+
type: "function",
189+
function: {
190+
name: "WriteFile",
191+
arguments: '{"path": "/write.txt", "content": "data"}',
192+
},
193+
},
194+
],
195+
},
196+
finish_reason: null,
197+
},
198+
],
199+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
200+
}),
201+
},
202+
{
203+
data: JSON.stringify({
204+
id: "c1",
205+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
206+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
207+
}),
208+
},
209+
{ data: "[DONE]" },
210+
]),
211+
}))
212+
213+
await mock.module("~/lib/rate-limit", () => ({
214+
checkRateLimit: (_: unknown) => {},
215+
}))
216+
const { server } = await import("~/server?stream-multiple-tools")
217+
const res = await server.request(
218+
"/v1beta/models/gemini-pro:streamGenerateContent",
219+
{
220+
method: "POST",
221+
headers: { "content-type": "application/json" },
222+
body: JSON.stringify({
223+
contents: [{ role: "user", parts: [{ text: "Read and write files" }] }],
224+
}),
225+
},
226+
)
227+
228+
expect(res.status).toBe(200)
229+
const body = await res.text()
230+
expect(
231+
body.includes(
232+
'"functionCall":{"name":"ReadFile","args":{"path":"/read.txt"}}',
233+
),
234+
).toBe(true)
235+
expect(
236+
body.includes(
237+
'"functionCall":{"name":"WriteFile","args":{"path":"/write.txt","content":"data"}}',
238+
),
239+
).toBe(true)
240+
})

0 commit comments

Comments
 (0)