Skip to content

Commit 9e0036b

Browse files
committed
Refactor translation tests: consolidate into integration tests, remove obsolete tests, and enhance error handling coverage
1 parent c207ee9 commit 9e0036b

16 files changed

Lines changed: 1613 additions & 1991 deletions

tests/generate-content/_test-utils.ts

Lines changed: 0 additions & 478 deletions
This file was deleted.
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import { expect, mock } from "bun:test"
2+
3+
import type {
4+
CapturedPayload,
5+
TestServer,
6+
TranslationCase,
7+
ToolCleanupExpectation,
8+
} from "../test-types"
9+
10+
export const GEMINI_PRO_URL = "/v1beta/models/gemini-pro:generateContent"
11+
12+
// ============================================================================
13+
// Payload Capture
14+
// ============================================================================
15+
16+
export async function setupPayloadCapture(mockResponse?: {
17+
content?: string
18+
finish_reason?: string
19+
usage?: {
20+
prompt_tokens: number
21+
completion_tokens: number
22+
total_tokens: number
23+
}
24+
}): Promise<CapturedPayload> {
25+
const capture: CapturedPayload = {} as CapturedPayload
26+
27+
const defaultResponse = {
28+
id: "test-id",
29+
choices: [
30+
{
31+
index: 0,
32+
message: {
33+
role: "assistant",
34+
content: mockResponse?.content ?? "ok",
35+
},
36+
finish_reason: mockResponse?.finish_reason ?? "stop",
37+
},
38+
],
39+
usage: mockResponse?.usage ?? {
40+
prompt_tokens: 1,
41+
completion_tokens: 1,
42+
total_tokens: 2,
43+
},
44+
}
45+
46+
await mock.module("~/services/copilot/create-chat-completions", () => ({
47+
createChatCompletions: (payload: CapturedPayload) => {
48+
Object.assign(capture, payload)
49+
return defaultResponse
50+
},
51+
}))
52+
53+
return capture
54+
}
55+
56+
// ============================================================================
57+
// Request Helpers
58+
// ============================================================================
59+
60+
export async function makeRequest(
61+
path: string,
62+
body: Record<string, unknown>,
63+
queryString?: string,
64+
): Promise<Response> {
65+
const query = queryString || `request-${Date.now()}`
66+
const serverModule = (await import(`~/server?${query}`)) as {
67+
server: TestServer
68+
}
69+
return serverModule.server.request(path, {
70+
method: "POST",
71+
headers: { "content-type": "application/json" },
72+
body: JSON.stringify(body),
73+
})
74+
}
75+
76+
// ============================================================================
77+
// Message Assertions
78+
// ============================================================================
79+
80+
function getMessagesByRole(
81+
payload: CapturedPayload,
82+
role: string,
83+
): Array<{ role: string; content?: string; tool_call_id?: string }> {
84+
return (payload.messages ?? []).filter((m) => m.role === role)
85+
}
86+
87+
export function expectMessageCounts(
88+
payload: CapturedPayload,
89+
expectations: {
90+
total?: number
91+
tool?: number
92+
assistant?: number
93+
assistantWithTools?: number
94+
user?: number
95+
},
96+
): void {
97+
const messages = payload.messages ?? []
98+
99+
if (expectations.total !== undefined) {
100+
expect(messages.length).toBeGreaterThanOrEqual(expectations.total)
101+
}
102+
103+
if (expectations.tool !== undefined) {
104+
const toolMessages = getMessagesByRole(payload, "tool")
105+
expect(toolMessages.length).toBeGreaterThanOrEqual(expectations.tool)
106+
}
107+
108+
if (expectations.assistant !== undefined) {
109+
const assistantMessages = getMessagesByRole(payload, "assistant")
110+
expect(assistantMessages.length).toBeGreaterThanOrEqual(
111+
expectations.assistant,
112+
)
113+
}
114+
115+
if (expectations.assistantWithTools !== undefined) {
116+
const assistantWithTools = messages.filter(
117+
(m) => m.role === "assistant" && m.tool_calls,
118+
)
119+
expect(assistantWithTools.length).toBeGreaterThanOrEqual(
120+
expectations.assistantWithTools,
121+
)
122+
}
123+
124+
if (expectations.user !== undefined) {
125+
const userMessages = getMessagesByRole(payload, "user")
126+
expect(userMessages.length).toBeGreaterThanOrEqual(expectations.user)
127+
}
128+
}
129+
130+
// ============================================================================
131+
// Tool Call Assertions
132+
// ============================================================================
133+
134+
export function expectUniqueToolCallIds(
135+
payload: CapturedPayload,
136+
maxExpected?: number,
137+
): void {
138+
const toolMessages = getMessagesByRole(payload, "tool")
139+
const toolCallIds = new Set(
140+
toolMessages.map((m) => m.tool_call_id).filter(Boolean),
141+
)
142+
143+
if (maxExpected !== undefined) {
144+
expect(toolCallIds.size).toBeLessThanOrEqual(maxExpected)
145+
} else {
146+
expect(toolCallIds.size).toBeGreaterThan(0)
147+
}
148+
}
149+
150+
export function expectToolCallIdFormat(payload: CapturedPayload): void {
151+
const messages = payload.messages ?? []
152+
const toolMessages = getMessagesByRole(payload, "tool")
153+
154+
for (const toolMsg of toolMessages) {
155+
expect(toolMsg.tool_call_id).toBeDefined()
156+
expect(typeof toolMsg.tool_call_id).toBe("string")
157+
}
158+
159+
const assistantWithTools = messages.filter(
160+
(m) => m.role === "assistant" && m.tool_calls,
161+
)
162+
for (const msg of assistantWithTools) {
163+
if (msg.tool_calls) {
164+
for (const toolCall of msg.tool_calls) {
165+
expect(toolCall.id.length).toBeLessThanOrEqual(40)
166+
}
167+
}
168+
}
169+
}
170+
171+
export function expectToolCleanup(
172+
payload: CapturedPayload,
173+
expectations: ToolCleanupExpectation,
174+
): void {
175+
const { noDuplicates, noEmptyFunctions } = expectations
176+
177+
if (noDuplicates && payload.tools) {
178+
const toolNames = payload.tools.map((t) => t.function.name)
179+
const uniqueNames = new Set(toolNames)
180+
expect(uniqueNames.size).toBe(toolNames.length)
181+
}
182+
183+
if (noEmptyFunctions && payload.tools) {
184+
const emptyFunctions = payload.tools.filter(
185+
(t) => !t.function.name || t.function.name.trim() === "",
186+
)
187+
expect(emptyFunctions.length).toBe(0)
188+
}
189+
}
190+
191+
// ============================================================================
192+
// Translation Case Builder
193+
// ============================================================================
194+
195+
export function buildTranslationCase(params: {
196+
name: string
197+
contents: Array<{
198+
role: string
199+
parts: Array<{ text?: string; functionCall?: unknown }>
200+
}>
201+
systemInstruction?: unknown
202+
tools?: Array<unknown>
203+
expectMessages?: number
204+
expectRoles?: Array<string>
205+
}): TranslationCase {
206+
return {
207+
name: params.name,
208+
input: {
209+
contents: params.contents,
210+
tools: params.tools,
211+
systemInstruction: params.systemInstruction,
212+
},
213+
expect: {
214+
messageCount: params.expectMessages,
215+
roles: params.expectRoles,
216+
},
217+
}
218+
}

0 commit comments

Comments
 (0)