Skip to content

Commit dba12f9

Browse files
author
root
committed
test: more tests for utils
1 parent c3b8c15 commit dba12f9

File tree

9 files changed

+953
-6
lines changed

9 files changed

+953
-6
lines changed

app/client/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export function getBearerToken(
238238
}
239239

240240
export function validString(x: string): boolean {
241-
return x?.length > 0;
241+
return !!x && x.trim().length > 0;
242242
}
243243

244244
export function getHeaders(ignoreHeaders: boolean = false) {

app/utils/merge.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
export function merge(target: any, source: any) {
22
Object.keys(source).forEach(function (key) {
3+
if (key === "__proto__" || key === "constructor") return; // skip unsafe keys
4+
35
if (
4-
source.hasOwnProperty(key) && // Check if the property is not inherited
6+
source.hasOwnProperty(key) &&
57
source[key] &&
6-
typeof source[key] === "object" || key === "__proto__" || key === "constructor"
8+
typeof source[key] === "object" &&
9+
!Array.isArray(source[key])
710
) {
811
merge((target[key] = target[key] || {}), source[key]);
9-
return;
12+
} else {
13+
target[key] = source[key];
1014
}
11-
target[key] = source[key];
1215
});
13-
}
16+
}

app/utils/utils.test.ts

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
import {
2+
trimTopic,
3+
isDalle3,
4+
getTimeoutMSByModel,
5+
getModelSizes,
6+
supportsCustomSize,
7+
showPlugins,
8+
getMessageTextContent,
9+
getMessageTextContentWithoutThinking,
10+
getMessageImages,
11+
semverCompare,
12+
getOperationId,
13+
} from "../../app/utils";
14+
import { ServiceProvider } from "../../app/constant";
15+
16+
describe("trimTopic", () => {
17+
test("should remove trailing punctuation", () => {
18+
expect(trimTopic("Hello World,")).toBe("Hello World");
19+
expect(trimTopic("Test。")).toBe("Test");
20+
expect(trimTopic("Question?")).toBe("Question");
21+
});
22+
23+
test("should remove quotes from both ends", () => {
24+
expect(trimTopic('"Hello"')).toBe("Hello");
25+
expect(trimTopic('"""Test"""')).toBe("Test");
26+
});
27+
28+
test("should remove asterisks", () => {
29+
expect(trimTopic("**Bold**")).toBe("Bold");
30+
expect(trimTopic("***Test***")).toBe("Test");
31+
});
32+
33+
test("should handle empty string", () => {
34+
expect(trimTopic("")).toBe("");
35+
});
36+
37+
test("should handle string with only punctuation", () => {
38+
expect(trimTopic("...")).toBe("");
39+
});
40+
41+
test("should not remove punctuation from middle", () => {
42+
expect(trimTopic("Hello, World")).toBe("Hello, World");
43+
});
44+
});
45+
46+
describe("isDalle3", () => {
47+
test("should return true for dall-e-3", () => {
48+
expect(isDalle3("dall-e-3")).toBe(true);
49+
});
50+
51+
test("should return false for other models", () => {
52+
expect(isDalle3("dall-e-2")).toBe(false);
53+
expect(isDalle3("gpt-4")).toBe(false);
54+
expect(isDalle3("")).toBe(false);
55+
});
56+
});
57+
58+
describe("getTimeoutMSByModel", () => {
59+
test("should return extended timeout for dall-e models", () => {
60+
expect(getTimeoutMSByModel("dall-e-3")).toBe(300000);
61+
expect(getTimeoutMSByModel("dalle-2")).toBe(300000);
62+
});
63+
64+
test("should return extended timeout for o1 models", () => {
65+
expect(getTimeoutMSByModel("o1-preview")).toBe(300000);
66+
expect(getTimeoutMSByModel("o3-mini")).toBe(300000);
67+
});
68+
69+
test("should return extended timeout for deepseek-r models", () => {
70+
expect(getTimeoutMSByModel("deepseek-r1")).toBe(300000);
71+
});
72+
73+
test("should return extended timeout for thinking models", () => {
74+
expect(getTimeoutMSByModel("gpt-4-thinking")).toBe(300000);
75+
});
76+
77+
test("should return normal timeout for regular models", () => {
78+
expect(getTimeoutMSByModel("gpt-4")).toBe(60000);
79+
expect(getTimeoutMSByModel("claude-3")).toBe(60000);
80+
});
81+
82+
test("should be case insensitive", () => {
83+
expect(getTimeoutMSByModel("DALL-E-3")).toBe(300000);
84+
expect(getTimeoutMSByModel("GPT-4")).toBe(60000);
85+
});
86+
});
87+
88+
describe("getModelSizes", () => {
89+
test("should return sizes for dall-e-3", () => {
90+
const sizes = getModelSizes("dall-e-3");
91+
expect(sizes).toEqual(["1024x1024", "1792x1024", "1024x1792"]);
92+
});
93+
94+
test("should return sizes for cogview models", () => {
95+
const sizes = getModelSizes("cogview-3");
96+
expect(sizes).toContain("1024x1024");
97+
expect(sizes.length).toBeGreaterThan(3);
98+
});
99+
100+
test("should return empty array for unsupported models", () => {
101+
expect(getModelSizes("gpt-4")).toEqual([]);
102+
expect(getModelSizes("claude-3")).toEqual([]);
103+
});
104+
105+
test("should be case insensitive for cogview", () => {
106+
const sizes = getModelSizes("CogView-3");
107+
expect(sizes.length).toBeGreaterThan(0);
108+
});
109+
});
110+
111+
describe("supportsCustomSize", () => {
112+
test("should return true for models with custom sizes", () => {
113+
expect(supportsCustomSize("dall-e-3")).toBe(true);
114+
expect(supportsCustomSize("cogview-3")).toBe(true);
115+
});
116+
117+
test("should return false for models without custom sizes", () => {
118+
expect(supportsCustomSize("gpt-4")).toBe(false);
119+
expect(supportsCustomSize("claude-3")).toBe(false);
120+
});
121+
});
122+
123+
describe("showPlugins", () => {
124+
test("should return true for OpenAI", () => {
125+
expect(showPlugins(ServiceProvider.OpenAI, "gpt-4")).toBe(true);
126+
});
127+
128+
test("should return true for Azure", () => {
129+
expect(showPlugins(ServiceProvider.Azure, "gpt-4")).toBe(true);
130+
});
131+
132+
test("should return true for Moonshot", () => {
133+
expect(showPlugins(ServiceProvider.Moonshot, "moonshot-v1")).toBe(true);
134+
});
135+
136+
test("should return true for ChatGLM", () => {
137+
expect(showPlugins(ServiceProvider.ChatGLM, "glm-4")).toBe(true);
138+
});
139+
140+
test("should return true for Anthropic non-claude-2", () => {
141+
expect(showPlugins(ServiceProvider.Anthropic, "claude-3")).toBe(true);
142+
});
143+
144+
test("should return false for Anthropic claude-2", () => {
145+
expect(showPlugins(ServiceProvider.Anthropic, "claude-2")).toBe(false);
146+
});
147+
148+
test("should return true for Google non-vision", () => {
149+
expect(showPlugins(ServiceProvider.Google, "gemini-pro")).toBe(true);
150+
});
151+
152+
test("should return false for Google vision", () => {
153+
expect(showPlugins(ServiceProvider.Google, "gemini-vision")).toBe(false);
154+
});
155+
156+
test("should return false for other providers", () => {
157+
expect(showPlugins(ServiceProvider.Baidu, "ernie")).toBe(false);
158+
});
159+
});
160+
161+
describe("getMessageTextContent", () => {
162+
test("should return string content directly", () => {
163+
const message = { role: "user" as const, content: "Hello" };
164+
expect(getMessageTextContent(message)).toBe("Hello");
165+
});
166+
167+
test("should extract text from multimodal content", () => {
168+
const message = {
169+
role: "user" as const,
170+
content: [
171+
{ type: "text" as const, text: "Hello" },
172+
{
173+
type: "image_url" as const,
174+
image_url: { url: "http://example.com" },
175+
},
176+
],
177+
};
178+
expect(getMessageTextContent(message)).toBe("Hello");
179+
});
180+
181+
test("should return empty string if no text content", () => {
182+
const message = {
183+
role: "user" as const,
184+
content: [
185+
{
186+
type: "image_url" as const,
187+
image_url: { url: "http://example.com" },
188+
},
189+
],
190+
};
191+
expect(getMessageTextContent(message)).toBe("");
192+
});
193+
194+
test("should handle empty content array", () => {
195+
const message = { role: "user" as const, content: [] };
196+
expect(getMessageTextContent(message)).toBe("");
197+
});
198+
});
199+
200+
describe("getMessageTextContentWithoutThinking", () => {
201+
test("should filter out thinking lines", () => {
202+
const message = {
203+
role: "user" as const,
204+
content: "Normal text\n> Thinking...\nMore text",
205+
};
206+
expect(getMessageTextContentWithoutThinking(message)).toBe(
207+
"Normal text\nMore text",
208+
);
209+
});
210+
211+
test("should handle content without thinking", () => {
212+
const message = { role: "user" as const, content: "Just normal text" };
213+
expect(getMessageTextContentWithoutThinking(message)).toBe(
214+
"Just normal text",
215+
);
216+
});
217+
218+
test("should handle multimodal content", () => {
219+
const message = {
220+
role: "user" as const,
221+
content: [{ type: "text" as const, text: "Hello\n> thinking\nWorld" }],
222+
};
223+
expect(getMessageTextContentWithoutThinking(message)).toBe("Hello\nWorld");
224+
});
225+
226+
test("should remove empty lines", () => {
227+
const message = {
228+
role: "user" as const,
229+
content: "Line1\n\n\nLine2",
230+
};
231+
expect(getMessageTextContentWithoutThinking(message)).toBe("Line1\nLine2");
232+
});
233+
});
234+
235+
describe("getMessageImages", () => {
236+
test("should extract image URLs from multimodal content", () => {
237+
const message = {
238+
role: "user" as const,
239+
content: [
240+
{ type: "text" as const, text: "Hello" },
241+
{ type: "image_url" as const, image_url: { url: "http://img1.com" } },
242+
{ type: "image_url" as const, image_url: { url: "http://img2.com" } },
243+
],
244+
};
245+
const urls = getMessageImages(message);
246+
expect(urls).toEqual(["http://img1.com", "http://img2.com"]);
247+
});
248+
249+
test("should return empty array for string content", () => {
250+
const message = { role: "user" as const, content: "Hello" };
251+
expect(getMessageImages(message)).toEqual([]);
252+
});
253+
254+
test("should return empty array if no images", () => {
255+
const message = {
256+
role: "user" as const,
257+
content: [{ type: "text" as const, text: "Hello" }],
258+
};
259+
expect(getMessageImages(message)).toEqual([]);
260+
});
261+
262+
test("should handle missing image_url", () => {
263+
const message = {
264+
role: "user" as const,
265+
content: [{ type: "image_url" as const }],
266+
};
267+
const urls = getMessageImages(message);
268+
expect(urls).toEqual([""]);
269+
});
270+
});
271+
272+
describe("semverCompare", () => {
273+
test("should compare versions correctly", () => {
274+
expect(semverCompare("1.0.0", "2.0.0")).toBeLessThan(0);
275+
expect(semverCompare("2.0.0", "1.0.0")).toBeGreaterThan(0);
276+
expect(semverCompare("1.0.0", "1.0.0")).toBe(0);
277+
});
278+
279+
test("should handle pre-release versions", () => {
280+
expect(semverCompare("1.0.0-alpha", "1.0.0")).toBeLessThan(0);
281+
expect(semverCompare("1.0.0", "1.0.0-alpha")).toBeGreaterThan(0);
282+
});
283+
284+
test("should handle patch versions", () => {
285+
expect(semverCompare("1.0.1", "1.0.2")).toBeLessThan(0);
286+
expect(semverCompare("1.0.10", "1.0.2")).toBeGreaterThan(0);
287+
});
288+
289+
test("should handle minor versions", () => {
290+
expect(semverCompare("1.1.0", "1.2.0")).toBeLessThan(0);
291+
expect(semverCompare("1.10.0", "1.2.0")).toBeGreaterThan(0);
292+
});
293+
294+
test("should handle major versions", () => {
295+
expect(semverCompare("2.0.0", "10.0.0")).toBeLessThan(0);
296+
});
297+
});
298+
299+
describe("getOperationId", () => {
300+
test("should return operationId if provided", () => {
301+
const op = { operationId: "customId", method: "GET", path: "/test" };
302+
expect(getOperationId(op)).toBe("customId");
303+
});
304+
305+
test("should generate ID from method and path", () => {
306+
const op = { method: "GET", path: "/users/list" };
307+
expect(getOperationId(op)).toBe("GET_users_list");
308+
});
309+
310+
test("should handle POST method", () => {
311+
const op = { method: "post", path: "/create" };
312+
expect(getOperationId(op)).toBe("POST_create");
313+
});
314+
315+
test("should replace slashes with underscores", () => {
316+
const op = { method: "GET", path: "/api/v1/users" };
317+
expect(getOperationId(op)).toBe("GET_api_v1_users");
318+
});
319+
320+
test("should handle root path", () => {
321+
const op = { method: "GET", path: "/" };
322+
expect(getOperationId(op)).toBe("GET_");
323+
});
324+
});

0 commit comments

Comments
 (0)