providers-actions.test.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. /**
  2. * 供应商管理模块 API 测试
  3. *
  4. * ⚠️ 状态:待重构为集成测试
  5. * 详见:tests/DIAGNOSIS-FINAL.md
  6. *
  7. * ---
  8. *
  9. * 测试范围:
  10. * - getProviders() - 获取供应商列表
  11. * - addProvider() - 添加供应商
  12. * - editProvider() - 编辑供应商
  13. * - removeProvider() - 删除供应商
  14. * - getProvidersHealthStatus() - 获取熔断器健康状态
  15. * - resetProviderCircuit() - 重置熔断器
  16. * - getProviderLimitUsage() - 获取供应商限额使用情况
  17. * - testProviderProxy() - 测试代理连接
  18. * - getUnmaskedProviderKey() - 获取完整密钥
  19. *
  20. * 测试场景:
  21. * - CRUD 操作
  22. * - 权重和优先级验证
  23. * - 代理配置验证
  24. * - 熔断器状态管理
  25. */
  26. import { beforeEach, describe, expect, test } from "vitest";
  27. import { callActionsRoute } from "../test-utils";
  28. const ADMIN_TOKEN = process.env.TEST_ADMIN_TOKEN || "test-admin-token";
  29. const USER_TOKEN = "test-user-token";
  30. // 辅助函数:调用供应商管理 API
  31. async function callProvidersApi(
  32. action: string,
  33. body: Record<string, unknown> = {},
  34. authToken = ADMIN_TOKEN
  35. ) {
  36. const { response, json } = await callActionsRoute({
  37. method: "POST",
  38. pathname: `/api/actions/providers/${action}`,
  39. authToken,
  40. body,
  41. });
  42. return { response, data: json as any };
  43. }
  44. // ⚠️ 跳过所有测试直到重构为集成测试
  45. describe.skip("供应商管理 - API 测试(待重构)", () => {
  46. test("未登录应返回空数组", async () => {
  47. const { response, data } = await callProvidersApi("getProviders", {}, undefined);
  48. expect(response.ok).toBe(true);
  49. expect(Array.isArray(data)).toBe(true);
  50. expect(data.length).toBe(0);
  51. });
  52. test("管理员应该可以查看所有供应商", async () => {
  53. const { response, data } = await callProvidersApi("getProviders");
  54. expect(response.ok).toBe(true);
  55. expect(Array.isArray(data)).toBe(true);
  56. });
  57. test("普通用户不能查看供应商列表", async () => {
  58. const { response, data } = await callProvidersApi("getProviders", {}, USER_TOKEN);
  59. expect(response.ok).toBe(true);
  60. expect(Array.isArray(data)).toBe(true);
  61. expect(data.length).toBe(0);
  62. });
  63. });
  64. describe.skip("供应商管理 - 添加供应商 (addProvider)", () => {
  65. test("应该成功添加 Claude 供应商", async () => {
  66. const { response, data } = await callProvidersApi("addProvider", {
  67. name: `测试供应商_Claude_${Date.now()}`,
  68. url: "https://api.anthropic.com",
  69. key: "sk-test-key-123456",
  70. provider_type: "claude",
  71. is_enabled: true,
  72. weight: 100,
  73. priority: 1,
  74. cost_multiplier: 1.0,
  75. });
  76. expect(response.ok).toBe(true);
  77. expect(data.ok).toBe(true);
  78. });
  79. test("应该成功添加 Codex 供应商", async () => {
  80. const { response, data } = await callProvidersApi("addProvider", {
  81. name: `测试供应商_Codex_${Date.now()}`,
  82. url: "https://api.openai.com",
  83. key: "sk-test-key-codex",
  84. provider_type: "codex",
  85. is_enabled: true,
  86. weight: 50,
  87. priority: 2,
  88. });
  89. expect(response.ok).toBe(true);
  90. expect(data.ok).toBe(true);
  91. });
  92. test("非管理员不能添加供应商", async () => {
  93. const { response, data } = await callProvidersApi(
  94. "addProvider",
  95. {
  96. name: "测试供应商",
  97. url: "https://api.example.com",
  98. key: "sk-test",
  99. provider_type: "claude",
  100. },
  101. USER_TOKEN
  102. );
  103. expect(response.ok).toBe(true);
  104. expect(data.ok).toBe(false);
  105. expect(data.error).toContain("无权限");
  106. });
  107. test("缺少必需参数应返回错误", async () => {
  108. const { response, data } = await callProvidersApi("addProvider", {
  109. name: "测试供应商",
  110. // 缺少 url 和 key
  111. provider_type: "claude",
  112. });
  113. expect(response.ok).toBe(true);
  114. expect(data.ok).toBe(false);
  115. expect(data.error).toBeDefined();
  116. });
  117. test("权重超出范围应返回验证失败", async () => {
  118. const { response, data } = await callProvidersApi("addProvider", {
  119. name: "测试供应商",
  120. url: "https://api.example.com",
  121. key: "sk-test",
  122. provider_type: "claude",
  123. weight: -1, // 应该 >= 0
  124. });
  125. expect(response.ok).toBe(true);
  126. expect(data.ok).toBe(false);
  127. expect(data.error).toBeDefined();
  128. });
  129. test("优先级超出范围应返回验证失败", async () => {
  130. const { response, data } = await callProvidersApi("addProvider", {
  131. name: "测试供应商",
  132. url: "https://api.example.com",
  133. key: "sk-test",
  134. provider_type: "claude",
  135. priority: 0, // 应该 >= 1
  136. });
  137. expect(response.ok).toBe(true);
  138. expect(data.ok).toBe(false);
  139. expect(data.error).toBeDefined();
  140. });
  141. test("添加带代理的供应商", async () => {
  142. const { response, data } = await callProvidersApi("addProvider", {
  143. name: `测试供应商_代理_${Date.now()}`,
  144. url: "https://api.anthropic.com",
  145. key: "sk-test-key",
  146. provider_type: "claude",
  147. proxy_url: "http://proxy.example.com:8080",
  148. proxy_fallback_to_direct: true,
  149. });
  150. expect(response.ok).toBe(true);
  151. expect(data.ok).toBe(true);
  152. });
  153. test("无效代理 URL 格式应返回错误", async () => {
  154. const { response, data } = await callProvidersApi("addProvider", {
  155. name: "测试供应商",
  156. url: "https://api.anthropic.com",
  157. key: "sk-test",
  158. provider_type: "claude",
  159. proxy_url: "invalid-proxy-url", // 无效格式
  160. });
  161. expect(response.ok).toBe(true);
  162. expect(data.ok).toBe(false);
  163. expect(data.error).toContain("代理地址格式无效");
  164. });
  165. test("添加带限额的供应商", async () => {
  166. const { response, data } = await callProvidersApi("addProvider", {
  167. name: `测试供应商_限额_${Date.now()}`,
  168. url: "https://api.anthropic.com",
  169. key: "sk-test-key",
  170. provider_type: "claude",
  171. limit_5h_usd: 10,
  172. limit_daily_usd: 50,
  173. limit_weekly_usd: 200,
  174. limit_monthly_usd: 500,
  175. limit_concurrent_sessions: 5,
  176. });
  177. expect(response.ok).toBe(true);
  178. expect(data.ok).toBe(true);
  179. });
  180. test("添加带熔断器配置的供应商", async () => {
  181. const { response, data } = await callProvidersApi("addProvider", {
  182. name: `测试供应商_熔断器_${Date.now()}`,
  183. url: "https://api.anthropic.com",
  184. key: "sk-test-key",
  185. provider_type: "claude",
  186. circuit_breaker_failure_threshold: 3,
  187. circuit_breaker_open_duration: 60000,
  188. circuit_breaker_half_open_success_threshold: 2,
  189. });
  190. expect(response.ok).toBe(true);
  191. expect(data.ok).toBe(true);
  192. });
  193. test("添加带模型重定向的供应商", async () => {
  194. const { response, data } = await callProvidersApi("addProvider", {
  195. name: `测试供应商_重定向_${Date.now()}`,
  196. url: "https://api.anthropic.com",
  197. key: "sk-test-key",
  198. provider_type: "claude",
  199. model_redirects: {
  200. "claude-3-opus": "claude-3-sonnet",
  201. "gpt-4": "claude-3-opus",
  202. },
  203. });
  204. expect(response.ok).toBe(true);
  205. expect(data.ok).toBe(true);
  206. });
  207. test("添加带分组标签的供应商", async () => {
  208. const { response, data } = await callProvidersApi("addProvider", {
  209. name: `测试供应商_分组_${Date.now()}`,
  210. url: "https://api.anthropic.com",
  211. key: "sk-test-key",
  212. provider_type: "claude",
  213. group_tag: "production",
  214. });
  215. expect(response.ok).toBe(true);
  216. expect(data.ok).toBe(true);
  217. });
  218. });
  219. describe.skip("供应商管理 - 编辑供应商 (editProvider)", () => {
  220. let testProviderId: number;
  221. beforeEach(async () => {
  222. // 创建测试供应商
  223. const { data: _data } = await callProvidersApi("addProvider", {
  224. name: `待编辑供应商_${Date.now()}`,
  225. url: "https://api.anthropic.com",
  226. key: "sk-test-key",
  227. provider_type: "claude",
  228. weight: 100,
  229. priority: 1,
  230. });
  231. // 获取创建的供应商 ID
  232. const providers = await callProvidersApi("getProviders");
  233. const createdProvider = providers.data.find(
  234. (p: any) => p.name === `待编辑供应商_${Date.now()}`
  235. );
  236. testProviderId = createdProvider?.id;
  237. });
  238. test("应该成功编辑供应商", async () => {
  239. if (!testProviderId) {
  240. console.log("跳过测试:无法创建测试供应商");
  241. return;
  242. }
  243. const { response, data } = await callProvidersApi("editProvider", {
  244. providerId: testProviderId,
  245. name: "已修改的供应商名",
  246. weight: 200,
  247. });
  248. expect(response.ok).toBe(true);
  249. expect(data.ok).toBe(true);
  250. });
  251. test("非管理员不能编辑供应商", async () => {
  252. if (!testProviderId) {
  253. console.log("跳过测试:无法创建测试供应商");
  254. return;
  255. }
  256. const { response, data } = await callProvidersApi(
  257. "editProvider",
  258. {
  259. providerId: testProviderId,
  260. name: "尝试修改",
  261. },
  262. USER_TOKEN
  263. );
  264. expect(response.ok).toBe(true);
  265. expect(data.ok).toBe(false);
  266. expect(data.error).toContain("无权限");
  267. });
  268. test("更新供应商权重", async () => {
  269. if (!testProviderId) {
  270. console.log("跳过测试:无法创建测试供应商");
  271. return;
  272. }
  273. const { response, data } = await callProvidersApi("editProvider", {
  274. providerId: testProviderId,
  275. weight: 150,
  276. });
  277. expect(response.ok).toBe(true);
  278. expect(data.ok).toBe(true);
  279. });
  280. test("更新供应商优先级", async () => {
  281. if (!testProviderId) {
  282. console.log("跳过测试:无法创建测试供应商");
  283. return;
  284. }
  285. const { response, data } = await callProvidersApi("editProvider", {
  286. providerId: testProviderId,
  287. priority: 5,
  288. });
  289. expect(response.ok).toBe(true);
  290. expect(data.ok).toBe(true);
  291. });
  292. test("更新供应商代理配置", async () => {
  293. if (!testProviderId) {
  294. console.log("跳过测试:无法创建测试供应商");
  295. return;
  296. }
  297. const { response, data } = await callProvidersApi("editProvider", {
  298. providerId: testProviderId,
  299. proxy_url: "http://proxy.example.com:3128",
  300. proxy_fallback_to_direct: true,
  301. });
  302. expect(response.ok).toBe(true);
  303. expect(data.ok).toBe(true);
  304. });
  305. test("更新供应商限额", async () => {
  306. if (!testProviderId) {
  307. console.log("跳过测试:无法创建测试供应商");
  308. return;
  309. }
  310. const { response, data } = await callProvidersApi("editProvider", {
  311. providerId: testProviderId,
  312. limit_5h_usd: 20,
  313. limit_daily_usd: 100,
  314. });
  315. expect(response.ok).toBe(true);
  316. expect(data.ok).toBe(true);
  317. });
  318. });
  319. describe.skip("供应商管理 - 删除供应商 (removeProvider)", () => {
  320. let testProviderId: number;
  321. beforeEach(async () => {
  322. // 创建待删除供应商
  323. await callProvidersApi("addProvider", {
  324. name: `待删除供应商_${Date.now()}`,
  325. url: "https://api.anthropic.com",
  326. key: "sk-test-key",
  327. provider_type: "claude",
  328. });
  329. const providers = await callProvidersApi("getProviders");
  330. const createdProvider = providers.data.find((p: any) => p.name.startsWith("待删除供应商_"));
  331. testProviderId = createdProvider?.id;
  332. });
  333. test("应该成功删除供应商", async () => {
  334. if (!testProviderId) {
  335. console.log("跳过测试:无法创建测试供应商");
  336. return;
  337. }
  338. const { response, data } = await callProvidersApi("removeProvider", {
  339. providerId: testProviderId,
  340. });
  341. expect(response.ok).toBe(true);
  342. expect(data.ok).toBe(true);
  343. });
  344. test("非管理员不能删除供应商", async () => {
  345. if (!testProviderId) {
  346. console.log("跳过测试:无法创建测试供应商");
  347. return;
  348. }
  349. const { response, data } = await callProvidersApi(
  350. "removeProvider",
  351. {
  352. providerId: testProviderId,
  353. },
  354. USER_TOKEN
  355. );
  356. expect(response.ok).toBe(true);
  357. expect(data.ok).toBe(false);
  358. expect(data.error).toContain("无权限");
  359. });
  360. test("删除不存在的供应商应返回错误", async () => {
  361. const { response, data } = await callProvidersApi("removeProvider", {
  362. providerId: 999999,
  363. });
  364. expect(response.ok).toBe(true);
  365. expect(data.ok).toBe(false);
  366. });
  367. });
  368. describe.skip("供应商管理 - 熔断器健康状态 (getProvidersHealthStatus)", () => {
  369. test("应该成功获取熔断器状态", async () => {
  370. const { response, data } = await callProvidersApi("getProvidersHealthStatus");
  371. expect(response.ok).toBe(true);
  372. expect(typeof data).toBe("object");
  373. // 熔断器状态应该是一个对象,键为供应商 ID
  374. });
  375. test("非管理员不能查看熔断器状态", async () => {
  376. const { response, data } = await callProvidersApi("getProvidersHealthStatus", {}, USER_TOKEN);
  377. expect(response.ok).toBe(true);
  378. expect(typeof data).toBe("object");
  379. expect(Object.keys(data).length).toBe(0);
  380. });
  381. });
  382. describe.skip("供应商管理 - 重置熔断器 (resetProviderCircuit)", () => {
  383. let testProviderId: number;
  384. beforeEach(async () => {
  385. // 创建测试供应商
  386. await callProvidersApi("addProvider", {
  387. name: `熔断器测试供应商_${Date.now()}`,
  388. url: "https://api.anthropic.com",
  389. key: "sk-test-key",
  390. provider_type: "claude",
  391. });
  392. const providers = await callProvidersApi("getProviders");
  393. const createdProvider = providers.data.find((p: any) => p.name.startsWith("熔断器测试供应商_"));
  394. testProviderId = createdProvider?.id;
  395. });
  396. test("应该成功重置熔断器", async () => {
  397. if (!testProviderId) {
  398. console.log("跳过测试:无法创建测试供应商");
  399. return;
  400. }
  401. const { response, data } = await callProvidersApi("resetProviderCircuit", {
  402. providerId: testProviderId,
  403. });
  404. expect(response.ok).toBe(true);
  405. expect(data.ok).toBe(true);
  406. });
  407. test("非管理员不能重置熔断器", async () => {
  408. if (!testProviderId) {
  409. console.log("跳过测试:无法创建测试供应商");
  410. return;
  411. }
  412. const { response, data } = await callProvidersApi(
  413. "resetProviderCircuit",
  414. {
  415. providerId: testProviderId,
  416. },
  417. USER_TOKEN
  418. );
  419. expect(response.ok).toBe(true);
  420. expect(data.ok).toBe(false);
  421. expect(data.error).toContain("无权限");
  422. });
  423. });
  424. describe.skip("供应商管理 - 获取供应商限额使用情况 (getProviderLimitUsage)", () => {
  425. let testProviderId: number;
  426. beforeEach(async () => {
  427. // 创建带限额的测试供应商
  428. await callProvidersApi("addProvider", {
  429. name: `限额测试供应商_${Date.now()}`,
  430. url: "https://api.anthropic.com",
  431. key: "sk-test-key",
  432. provider_type: "claude",
  433. limit_5h_usd: 10,
  434. limit_daily_usd: 50,
  435. limit_weekly_usd: 200,
  436. limit_monthly_usd: 500,
  437. });
  438. const providers = await callProvidersApi("getProviders");
  439. const createdProvider = providers.data.find((p: any) => p.name.startsWith("限额测试供应商_"));
  440. testProviderId = createdProvider?.id;
  441. });
  442. test("应该成功获取供应商限额使用情况", async () => {
  443. if (!testProviderId) {
  444. console.log("跳过测试:无法创建测试供应商");
  445. return;
  446. }
  447. const { response, data } = await callProvidersApi("getProviderLimitUsage", {
  448. providerId: testProviderId,
  449. });
  450. expect(response.ok).toBe(true);
  451. expect(data.ok).toBe(true);
  452. expect(data.data).toBeDefined();
  453. expect(data.data.cost5h).toBeDefined();
  454. expect(data.data.costDaily).toBeDefined();
  455. expect(data.data.costWeekly).toBeDefined();
  456. expect(data.data.costMonthly).toBeDefined();
  457. expect(data.data.concurrentSessions).toBeDefined();
  458. });
  459. test("非管理员不能查看供应商限额", async () => {
  460. if (!testProviderId) {
  461. console.log("跳过测试:无法创建测试供应商");
  462. return;
  463. }
  464. const { response, data } = await callProvidersApi(
  465. "getProviderLimitUsage",
  466. {
  467. providerId: testProviderId,
  468. },
  469. USER_TOKEN
  470. );
  471. expect(response.ok).toBe(true);
  472. expect(data.ok).toBe(false);
  473. expect(data.error).toContain("无权限");
  474. });
  475. });
  476. describe.skip("供应商管理 - 测试代理连接 (testProviderProxy)", () => {
  477. test("应该成功测试无代理连接", async () => {
  478. const { response, data } = await callProvidersApi("testProviderProxy", {
  479. providerUrl: "https://api.anthropic.com",
  480. proxyUrl: null,
  481. });
  482. expect(response.ok).toBe(true);
  483. expect(data.ok).toBe(true);
  484. expect(data.data).toBeDefined();
  485. expect(data.data.success).toBeDefined();
  486. expect(data.data.message).toBeDefined();
  487. });
  488. test("无效的代理 URL 应返回错误", async () => {
  489. const { response, data } = await callProvidersApi("testProviderProxy", {
  490. providerUrl: "https://api.anthropic.com",
  491. proxyUrl: "invalid-proxy",
  492. });
  493. expect(response.ok).toBe(true);
  494. expect(data.ok).toBe(true);
  495. expect(data.data.success).toBe(false);
  496. expect(data.data.message).toContain("代理地址格式无效");
  497. });
  498. test("非管理员不能测试代理连接", async () => {
  499. const { response, data } = await callProvidersApi(
  500. "testProviderProxy",
  501. {
  502. providerUrl: "https://api.anthropic.com",
  503. },
  504. USER_TOKEN
  505. );
  506. expect(response.ok).toBe(true);
  507. expect(data.ok).toBe(false);
  508. expect(data.error).toContain("无权限");
  509. });
  510. });
  511. describe.skip("供应商管理 - 获取完整密钥 (getUnmaskedProviderKey)", () => {
  512. let testProviderId: number;
  513. beforeEach(async () => {
  514. // 创建测试供应商
  515. await callProvidersApi("addProvider", {
  516. name: `密钥测试供应商_${Date.now()}`,
  517. url: "https://api.anthropic.com",
  518. key: "sk-test-complete-key-123456",
  519. provider_type: "claude",
  520. });
  521. const providers = await callProvidersApi("getProviders");
  522. const createdProvider = providers.data.find((p: any) => p.name.startsWith("密钥测试供应商_"));
  523. testProviderId = createdProvider?.id;
  524. });
  525. test("管理员应该可以获取完整密钥", async () => {
  526. if (!testProviderId) {
  527. console.log("跳过测试:无法创建测试供应商");
  528. return;
  529. }
  530. const { response, data } = await callProvidersApi("getUnmaskedProviderKey", {
  531. id: testProviderId,
  532. });
  533. expect(response.ok).toBe(true);
  534. expect(data.ok).toBe(true);
  535. expect(data.data).toBeDefined();
  536. expect(data.data.key).toBeDefined();
  537. expect(data.data.key).toMatch(/^sk-/);
  538. });
  539. test("非管理员不能获取完整密钥", async () => {
  540. if (!testProviderId) {
  541. console.log("跳过测试:无法创建测试供应商");
  542. return;
  543. }
  544. const { response, data } = await callProvidersApi(
  545. "getUnmaskedProviderKey",
  546. {
  547. id: testProviderId,
  548. },
  549. USER_TOKEN
  550. );
  551. expect(response.ok).toBe(true);
  552. expect(data.ok).toBe(false);
  553. expect(data.error).toContain("权限不足");
  554. });
  555. test("获取不存在的供应商密钥应返回错误", async () => {
  556. const { response, data } = await callProvidersApi("getUnmaskedProviderKey", {
  557. id: 999999,
  558. });
  559. expect(response.ok).toBe(true);
  560. expect(data.ok).toBe(false);
  561. expect(data.error).toContain("供应商不存在");
  562. });
  563. });
  564. describe.skip("供应商管理 - 响应格式验证", () => {
  565. test("所有成功响应应符合 ActionResult 格式", async () => {
  566. const { response, data } = await callProvidersApi("getProviders");
  567. expect(response.ok).toBe(true);
  568. expect(Array.isArray(data)).toBe(true);
  569. });
  570. test("所有错误响应应符合 ActionResult 格式", async () => {
  571. const { response, data } = await callProvidersApi(
  572. "addProvider",
  573. {
  574. name: "测试供应商",
  575. url: "https://api.example.com",
  576. key: "sk-test",
  577. provider_type: "claude",
  578. },
  579. USER_TOKEN
  580. );
  581. expect(response.ok).toBe(true);
  582. expect(data).toHaveProperty("ok");
  583. expect(data.ok).toBe(false);
  584. expect(data).toHaveProperty("error");
  585. expect(typeof data.error).toBe("string");
  586. });
  587. });