-
-
Notifications
You must be signed in to change notification settings - Fork 181
test(models): 为 /v1/models 端点添加单元测试 #523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -10,7 +10,7 @@ import { ProxyProviderResolver } from "../proxy/provider-selector"; | |||||
|
|
||||||
| type ResponseFormat = "openai" | "anthropic" | "gemini" | "codex"; | ||||||
|
|
||||||
| interface FetchedModel { | ||||||
| export interface FetchedModel { | ||||||
| id: string; | ||||||
| displayName?: string; | ||||||
| createdAt?: string; | ||||||
|
|
@@ -125,7 +125,7 @@ function mapResponseFormatToClientFormat(format: ResponseFormat): ClientFormat { | |||||
| /** | ||||||
| * 根据模型 ID 推断所有者 | ||||||
| */ | ||||||
| function inferOwner(modelId: string): string { | ||||||
| export function inferOwner(modelId: string): string { | ||||||
| if (modelId.startsWith("claude-")) return "anthropic"; | ||||||
| if (modelId.startsWith("gpt-") || modelId.startsWith("o1") || modelId.startsWith("o3")) | ||||||
| return "openai"; | ||||||
|
|
@@ -256,7 +256,7 @@ async function fetchModelsFromProvider(provider: Provider): Promise<FetchedModel | |||||
| /** | ||||||
| * 根据客户端格式获取需要决策的 providerType 列表 | ||||||
| */ | ||||||
| function getProviderTypesForFormat(clientFormat: ClientFormat): Provider["providerType"][] { | ||||||
| export function getProviderTypesForFormat(clientFormat: ClientFormat): Provider["providerType"][] { | ||||||
| switch (clientFormat) { | ||||||
| case "claude": | ||||||
| return ["claude", "claude-auth"]; | ||||||
|
|
@@ -298,7 +298,7 @@ async function getAvailableModels( | |||||
| /** | ||||||
| * 格式化为 OpenAI 响应 | ||||||
| */ | ||||||
| function formatOpenAIResponse(models: FetchedModel[]): object { | ||||||
| export function formatOpenAIResponse(models: FetchedModel[]): object { | ||||||
| const now = Math.floor(Date.now() / 1000); | ||||||
| const data = models.map((m) => ({ | ||||||
| id: m.id, | ||||||
|
|
@@ -313,7 +313,7 @@ function formatOpenAIResponse(models: FetchedModel[]): object { | |||||
| /** | ||||||
| * 格式化为 Anthropic 响应 | ||||||
| */ | ||||||
| function formatAnthropicResponse(models: FetchedModel[]): object { | ||||||
| export function formatAnthropicResponse(models: FetchedModel[]): object { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 与
Suggested change
|
||||||
| const now = new Date().toISOString(); | ||||||
| const data = models.map((m) => ({ | ||||||
| id: m.id, | ||||||
|
|
@@ -328,7 +328,7 @@ function formatAnthropicResponse(models: FetchedModel[]): object { | |||||
| /** | ||||||
| * 格式化为 Gemini 响应 | ||||||
| */ | ||||||
| function formatGeminiResponse(models: FetchedModel[]): object { | ||||||
| export function formatGeminiResponse(models: FetchedModel[]): object { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
| const geminiModels = models.map((m) => ({ | ||||||
| name: `models/${m.id}`, | ||||||
| displayName: m.displayName || m.id, | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,226 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { describe, expect, test } from "vitest"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| formatAnthropicResponse, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| formatGeminiResponse, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| formatOpenAIResponse, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| getProviderTypesForFormat, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| inferOwner, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| type FetchedModel, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@/app/v1/_lib/models/available-models"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("inferOwner - 根据模型 ID 推断所有者", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("Anthropic 模型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("claude-* 模型应返回 anthropic", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("claude-3-opus-20240229")).toBe("anthropic"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("claude-3-sonnet-20240229")).toBe("anthropic"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("claude-3-haiku-20240307")).toBe("anthropic"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("claude-2.1")).toBe("anthropic"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("claude-instant-1.2")).toBe("anthropic"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+19
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这个测试用例可以通过 test.each([
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-3-haiku-20240307",
"claude-2.1",
"claude-instant-1.2",
])("claude-* 模型 '%s' 应返回 anthropic", (modelId) => {
expect(inferOwner(modelId)).toBe("anthropic");
}); |
||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("OpenAI 模型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("gpt-* 模型应返回 openai", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("gpt-4")).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("gpt-4-turbo")).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("gpt-3.5-turbo")).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("gpt-4o")).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("o1* 模型应返回 openai", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("o1-preview")).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("o1-mini")).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("o1")).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("o3* 模型应返回 openai", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("o3-mini")).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("o3")).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("Google 模型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("gemini-* 模型应返回 google", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("gemini-pro")).toBe("google"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("gemini-1.5-pro")).toBe("google"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("gemini-1.5-flash")).toBe("google"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("gemini-2.0-flash-exp")).toBe("google"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("DeepSeek 模型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("deepseek* 模型应返回 deepseek", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("deepseek-chat")).toBe("deepseek"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("deepseek-coder")).toBe("deepseek"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("deepseek-v3")).toBe("deepseek"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("Alibaba 模型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("qwen* 模型应返回 alibaba", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("qwen-turbo")).toBe("alibaba"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("qwen-plus")).toBe("alibaba"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("qwen-max")).toBe("alibaba"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("未知模型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("无法识别的模型应返回 unknown", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("llama-2-70b")).toBe("unknown"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("mistral-7b")).toBe("unknown"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(inferOwner("custom-model")).toBe("unknown"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("getProviderTypesForFormat - 客户端格式到 Provider 类型映射", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("claude 格式应返回 claude 和 claude-auth 类型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(getProviderTypesForFormat("claude")).toEqual(["claude", "claude-auth"]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("openai 格式应返回 codex 和 openai-compatible 类型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(getProviderTypesForFormat("openai")).toEqual(["codex", "openai-compatible"]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("gemini 格式应返回 gemini 和 gemini-cli 类型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(getProviderTypesForFormat("gemini")).toEqual(["gemini", "gemini-cli"]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("gemini-cli 格式应返回 gemini 和 gemini-cli 类型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(getProviderTypesForFormat("gemini-cli")).toEqual(["gemini", "gemini-cli"]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("response 格式应仅返回 codex 类型", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(getProviderTypesForFormat("response")).toEqual(["codex"]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("formatOpenAIResponse - OpenAI 格式响应", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("空模型列表应返回空 data 数组", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatOpenAIResponse([]) as { object: string; data: unknown[] }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.object).toBe("list"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data).toEqual([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("应正确格式化模型列表", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const models: FetchedModel[] = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { id: "gpt-4" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { id: "claude-3-opus-20240229" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { id: "gemini-pro" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatOpenAIResponse(models) as { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| object: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| data: Array<{ id: string; object: string; created: number; owned_by: string }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.object).toBe("list"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data).toHaveLength(3); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].id).toBe("gpt-4"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].object).toBe("model"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].owned_by).toBe("openai"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(typeof result.data[0].created).toBe("number"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[1].id).toBe("claude-3-opus-20240229"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[1].owned_by).toBe("anthropic"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[2].id).toBe("gemini-pro"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[2].owned_by).toBe("google"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("created 时间戳应为当前时间(秒)", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const before = Math.floor(Date.now() / 1000); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatOpenAIResponse([{ id: "test" }]) as { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| data: Array<{ created: number }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const after = Math.floor(Date.now() / 1000); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].created).toBeGreaterThanOrEqual(before); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].created).toBeLessThanOrEqual(after); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+132
to
+141
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这个时间戳测试依赖于实际执行时间,可能会变得不稳定。使用 要使用
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("formatAnthropicResponse - Anthropic 格式响应", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("空模型列表应返回空 data 数组", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatAnthropicResponse([]) as { data: unknown[]; has_more: boolean }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data).toEqual([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.has_more).toBe(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("应正确格式化模型列表", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const models: FetchedModel[] = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { id: "claude-3-opus-20240229", displayName: "Claude 3 Opus", createdAt: "2024-02-29T00:00:00Z" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { id: "claude-3-sonnet-20240229" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatAnthropicResponse(models) as { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| data: Array<{ id: string; type: string; display_name: string; created_at: string }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| has_more: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.has_more).toBe(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data).toHaveLength(2); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].id).toBe("claude-3-opus-20240229"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].type).toBe("model"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].display_name).toBe("Claude 3 Opus"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].created_at).toBe("2024-02-29T00:00:00Z"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[1].id).toBe("claude-3-sonnet-20240229"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[1].display_name).toBe("claude-3-sonnet-20240229"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[1].created_at).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("缺少 displayName 时应使用 id 作为 display_name", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatAnthropicResponse([{ id: "test-model" }]) as { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| data: Array<{ display_name: string }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.data[0].display_name).toBe("test-model"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("formatGeminiResponse - Gemini 格式响应", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test("空模型列表应返回空 models 数组", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatGeminiResponse([]) as { models: unknown[] }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.models).toEqual([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("应正确格式化模型列表", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const models: FetchedModel[] = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { id: "gemini-pro", displayName: "Gemini Pro" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { id: "gemini-1.5-flash" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatGeminiResponse(models) as { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| models: Array<{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| name: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| displayName: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| supportedGenerationMethods: string[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.models).toHaveLength(2); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.models[0].name).toBe("models/gemini-pro"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.models[0].displayName).toBe("Gemini Pro"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.models[0].supportedGenerationMethods).toEqual(["generateContent"]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.models[1].name).toBe("models/gemini-1.5-flash"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.models[1].displayName).toBe("gemini-1.5-flash"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("模型名称应添加 models/ 前缀", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatGeminiResponse([{ id: "test-model" }]) as { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| models: Array<{ name: string }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.models[0].name).toBe("models/test-model"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| test("缺少 displayName 时应使用 id", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = formatGeminiResponse([{ id: "test-model" }]) as { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| models: Array<{ displayName: string }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.models[0].displayName).toBe("test-model"); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
为了增强类型安全并使代码更易于维护,建议为
formatOpenAIResponse函数的返回值定义一个明确的类型,而不是使用通用的object类型。这可以避免在测试代码中进行类型断言,并使函数签名更具自述性。虽然使用内联类型会使签名变长,但它能立即提供类型安全。未来可以考虑将其重构为独立的接口。