available-models.test.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { describe, expect, test, vi } from "vitest";
  2. // Mock dependencies that cause import issues
  3. vi.mock("@/lib/proxy-agent", () => ({
  4. createProxyAgentForProvider: vi.fn(),
  5. }));
  6. vi.mock("@/repository/key", () => ({
  7. validateApiKeyAndGetUser: vi.fn(),
  8. }));
  9. vi.mock("@/app/v1/_lib/proxy/provider-selector", () => ({
  10. ProxyProviderResolver: {
  11. selectProviderByType: vi.fn(),
  12. },
  13. }));
  14. import {
  15. formatAnthropicResponse,
  16. formatGeminiResponse,
  17. formatOpenAIResponse,
  18. getProviderTypesForFormat,
  19. inferOwner,
  20. type FetchedModel,
  21. } from "@/app/v1/_lib/models/available-models";
  22. import type {
  23. AnthropicModelsResponse,
  24. GeminiModelsResponse,
  25. OpenAIModelsResponse,
  26. } from "@/types/models";
  27. describe("inferOwner - 根据模型 ID 推断所有者", () => {
  28. describe("Anthropic 模型", () => {
  29. test("claude-* 模型应返回 anthropic", () => {
  30. expect(inferOwner("claude-3-opus-20240229")).toBe("anthropic");
  31. expect(inferOwner("claude-3-sonnet-20240229")).toBe("anthropic");
  32. expect(inferOwner("claude-3-haiku-20240307")).toBe("anthropic");
  33. expect(inferOwner("claude-2.1")).toBe("anthropic");
  34. expect(inferOwner("claude-instant-1.2")).toBe("anthropic");
  35. });
  36. });
  37. describe("OpenAI 模型", () => {
  38. test("gpt-* 模型应返回 openai", () => {
  39. expect(inferOwner("gpt-4")).toBe("openai");
  40. expect(inferOwner("gpt-4-turbo")).toBe("openai");
  41. expect(inferOwner("gpt-3.5-turbo")).toBe("openai");
  42. expect(inferOwner("gpt-4o")).toBe("openai");
  43. });
  44. test("o1* 模型应返回 openai", () => {
  45. expect(inferOwner("o1-preview")).toBe("openai");
  46. expect(inferOwner("o1-mini")).toBe("openai");
  47. expect(inferOwner("o1")).toBe("openai");
  48. });
  49. test("o3* 模型应返回 openai", () => {
  50. expect(inferOwner("o3-mini")).toBe("openai");
  51. expect(inferOwner("o3")).toBe("openai");
  52. });
  53. });
  54. describe("Google 模型", () => {
  55. test("gemini-* 模型应返回 google", () => {
  56. expect(inferOwner("gemini-pro")).toBe("google");
  57. expect(inferOwner("gemini-1.5-pro")).toBe("google");
  58. expect(inferOwner("gemini-1.5-flash")).toBe("google");
  59. expect(inferOwner("gemini-2.0-flash-exp")).toBe("google");
  60. });
  61. });
  62. describe("DeepSeek 模型", () => {
  63. test("deepseek* 模型应返回 deepseek", () => {
  64. expect(inferOwner("deepseek-chat")).toBe("deepseek");
  65. expect(inferOwner("deepseek-coder")).toBe("deepseek");
  66. expect(inferOwner("deepseek-v3")).toBe("deepseek");
  67. });
  68. });
  69. describe("Alibaba 模型", () => {
  70. test("qwen* 模型应返回 alibaba", () => {
  71. expect(inferOwner("qwen-turbo")).toBe("alibaba");
  72. expect(inferOwner("qwen-plus")).toBe("alibaba");
  73. expect(inferOwner("qwen-max")).toBe("alibaba");
  74. });
  75. });
  76. describe("未知模型", () => {
  77. test("无法识别的模型应返回 unknown", () => {
  78. expect(inferOwner("llama-2-70b")).toBe("unknown");
  79. expect(inferOwner("mistral-7b")).toBe("unknown");
  80. expect(inferOwner("custom-model")).toBe("unknown");
  81. });
  82. });
  83. });
  84. describe("getProviderTypesForFormat - 客户端格式到 Provider 类型映射", () => {
  85. test("claude 格式应返回 claude 和 claude-auth 类型", () => {
  86. expect(getProviderTypesForFormat("claude")).toEqual(["claude", "claude-auth"]);
  87. });
  88. test("openai 格式应返回 codex 和 openai-compatible 类型", () => {
  89. expect(getProviderTypesForFormat("openai")).toEqual(["codex", "openai-compatible"]);
  90. });
  91. test("gemini 格式应返回 gemini 和 gemini-cli 类型", () => {
  92. expect(getProviderTypesForFormat("gemini")).toEqual(["gemini", "gemini-cli"]);
  93. });
  94. test("gemini-cli 格式应返回 gemini 和 gemini-cli 类型", () => {
  95. expect(getProviderTypesForFormat("gemini-cli")).toEqual(["gemini", "gemini-cli"]);
  96. });
  97. test("response 格式应仅返回 codex 类型", () => {
  98. expect(getProviderTypesForFormat("response")).toEqual(["codex"]);
  99. });
  100. });
  101. describe("formatOpenAIResponse - OpenAI 格式响应", () => {
  102. test("空模型列表应返回空 data 数组", () => {
  103. const result: OpenAIModelsResponse = formatOpenAIResponse([]);
  104. expect(result.object).toBe("list");
  105. expect(result.data).toEqual([]);
  106. });
  107. test("应正确格式化模型列表", () => {
  108. const models: FetchedModel[] = [
  109. { id: "gpt-4" },
  110. { id: "claude-3-opus-20240229" },
  111. { id: "gemini-pro" },
  112. ];
  113. const result: OpenAIModelsResponse = formatOpenAIResponse(models);
  114. expect(result.object).toBe("list");
  115. expect(result.data).toHaveLength(3);
  116. expect(result.data[0].id).toBe("gpt-4");
  117. expect(result.data[0].object).toBe("model");
  118. expect(result.data[0].owned_by).toBe("openai");
  119. expect(typeof result.data[0].created).toBe("number");
  120. expect(result.data[1].id).toBe("claude-3-opus-20240229");
  121. expect(result.data[1].owned_by).toBe("anthropic");
  122. expect(result.data[2].id).toBe("gemini-pro");
  123. expect(result.data[2].owned_by).toBe("google");
  124. });
  125. test("created 时间戳应为当前时间(秒)", () => {
  126. const before = Math.floor(Date.now() / 1000);
  127. const result: OpenAIModelsResponse = formatOpenAIResponse([{ id: "test" }]);
  128. const after = Math.floor(Date.now() / 1000);
  129. expect(result.data[0].created).toBeGreaterThanOrEqual(before);
  130. expect(result.data[0].created).toBeLessThanOrEqual(after);
  131. });
  132. });
  133. describe("formatAnthropicResponse - Anthropic 格式响应", () => {
  134. test("空模型列表应返回空 data 数组", () => {
  135. const result: AnthropicModelsResponse = formatAnthropicResponse([]);
  136. expect(result.data).toEqual([]);
  137. expect(result.has_more).toBe(false);
  138. });
  139. test("应正确格式化模型列表", () => {
  140. const models: FetchedModel[] = [
  141. {
  142. id: "claude-3-opus-20240229",
  143. displayName: "Claude 3 Opus",
  144. createdAt: "2024-02-29T00:00:00Z",
  145. },
  146. { id: "claude-3-sonnet-20240229" },
  147. ];
  148. const result: AnthropicModelsResponse = formatAnthropicResponse(models);
  149. expect(result.has_more).toBe(false);
  150. expect(result.data).toHaveLength(2);
  151. expect(result.data[0].id).toBe("claude-3-opus-20240229");
  152. expect(result.data[0].type).toBe("model");
  153. expect(result.data[0].display_name).toBe("Claude 3 Opus");
  154. expect(result.data[0].created_at).toBe("2024-02-29T00:00:00Z");
  155. expect(result.data[1].id).toBe("claude-3-sonnet-20240229");
  156. expect(result.data[1].display_name).toBe("claude-3-sonnet-20240229");
  157. expect(result.data[1].created_at).toBeDefined();
  158. });
  159. test("缺少 displayName 时应使用 id 作为 display_name", () => {
  160. const result: AnthropicModelsResponse = formatAnthropicResponse([{ id: "test-model" }]);
  161. expect(result.data[0].display_name).toBe("test-model");
  162. });
  163. });
  164. describe("formatGeminiResponse - Gemini 格式响应", () => {
  165. test("空模型列表应返回空 models 数组", () => {
  166. const result: GeminiModelsResponse = formatGeminiResponse([]);
  167. expect(result.models).toEqual([]);
  168. });
  169. test("应正确格式化模型列表", () => {
  170. const models: FetchedModel[] = [
  171. { id: "gemini-pro", displayName: "Gemini Pro" },
  172. { id: "gemini-1.5-flash" },
  173. ];
  174. const result: GeminiModelsResponse = formatGeminiResponse(models);
  175. expect(result.models).toHaveLength(2);
  176. expect(result.models[0].name).toBe("models/gemini-pro");
  177. expect(result.models[0].displayName).toBe("Gemini Pro");
  178. expect(result.models[0].supportedGenerationMethods).toEqual(["generateContent"]);
  179. expect(result.models[1].name).toBe("models/gemini-1.5-flash");
  180. expect(result.models[1].displayName).toBe("gemini-1.5-flash");
  181. });
  182. test("模型名称应添加 models/ 前缀", () => {
  183. const result: GeminiModelsResponse = formatGeminiResponse([{ id: "test-model" }]);
  184. expect(result.models[0].name).toBe("models/test-model");
  185. });
  186. test("缺少 displayName 时应使用 id", () => {
  187. const result: GeminiModelsResponse = formatGeminiResponse([{ id: "test-model" }]);
  188. expect(result.models[0].displayName).toBe("test-model");
  189. });
  190. });