users-actions.test.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. /**
  2. * 用户管理模块 API 测试
  3. *
  4. * ⚠️ 状态:待重构为集成测试
  5. *
  6. * 当前问题:
  7. * - 这些测试试图在单元测试环境中运行需要完整 Next.js 运行时的 Server Actions
  8. * - Server Actions 使用了 cookies()、getTranslations()、revalidatePath() 等 Next.js 特定 API
  9. * - 这些 API 需要 Next.js 的请求上下文(AsyncLocalStorage),在 Vitest 环境无法提供
  10. *
  11. * 解决方案:
  12. * - 方案 A(推荐):重写为集成测试 - 启动真实的 Next.js 服务器,通过 HTTP 请求测试
  13. * - 方案 B:单元测试 Repository 层 - 只测试不依赖 Next.js 运行时的纯业务逻辑
  14. *
  15. * 详见:tests/DIAGNOSIS-FINAL.md
  16. *
  17. * ---
  18. *
  19. * 测试范围:
  20. * - getUsers() - 获取用户列表
  21. * - addUser() - 创建用户
  22. * - editUser() - 编辑用户
  23. * - removeUser() - 删除用户
  24. * - toggleUserEnabled() - 启用/禁用用户
  25. * - renewUser() - 续期用户
  26. * - getUserLimitUsage() - 获取用户限额使用情况
  27. *
  28. * 测试场景:
  29. * - 正常场景(CRUD 操作)
  30. * - 参数验证(边界值、非法值)
  31. * - 权限控制(管理员 vs 普通用户)
  32. * - 错误处理
  33. */
  34. import { beforeEach, describe, expect, test } from "vitest";
  35. import { callActionsRoute } from "../test-utils";
  36. // 测试用管理员 Token(实际使用时需要有效的 token)
  37. const ADMIN_TOKEN = process.env.TEST_ADMIN_TOKEN || "test-admin-token";
  38. const USER_TOKEN = "test-user-token";
  39. // 辅助函数:调用用户管理 API
  40. async function callUsersApi(
  41. action: string,
  42. body: Record<string, unknown> = {},
  43. authToken = ADMIN_TOKEN
  44. ) {
  45. const { response, json } = await callActionsRoute({
  46. method: "POST",
  47. pathname: `/api/actions/users/${action}`,
  48. authToken,
  49. body,
  50. });
  51. return { response, data: json as any };
  52. }
  53. // ⚠️ 跳过所有测试直到重构为集成测试
  54. describe.skip("用户管理 - API 测试(待重构)", () => {
  55. test("未登录应返回空数组", async () => {
  56. const { response, data } = await callUsersApi("getUsers", {}, undefined);
  57. expect(response.ok).toBe(true);
  58. expect(Array.isArray(data)).toBe(true);
  59. expect(data.length).toBe(0);
  60. });
  61. test("管理员应该可以查看所有用户", async () => {
  62. const { response, data } = await callUsersApi("getUsers");
  63. expect(response.ok).toBe(true);
  64. expect(Array.isArray(data)).toBe(true);
  65. });
  66. test("普通用户只能看到自己", async () => {
  67. const { response, data } = await callUsersApi("getUsers", {}, USER_TOKEN);
  68. expect(response.ok).toBe(true);
  69. // 由于测试环境的 USER_TOKEN 无法查询到真实用户,此处会返回空数组
  70. expect(Array.isArray(data)).toBe(true);
  71. });
  72. });
  73. describe.skip("用户管理 - 创建用户 (addUser)", () => {
  74. test("应该成功创建用户", async () => {
  75. const { response, data } = await callUsersApi("addUser", {
  76. name: `测试用户_${Date.now()}`,
  77. note: "API 测试创建的用户",
  78. rpm: 60,
  79. dailyQuota: 10,
  80. isEnabled: true,
  81. });
  82. expect(response.ok).toBe(true);
  83. expect(data.ok).toBe(true);
  84. expect(data.data).toBeDefined();
  85. expect(data.data.user).toBeDefined();
  86. expect(data.data.defaultKey).toBeDefined();
  87. expect(data.data.defaultKey.key).toMatch(/^sk-[a-f0-9]{32}$/);
  88. });
  89. test("非管理员不能创建用户", async () => {
  90. const { response, data } = await callUsersApi(
  91. "addUser",
  92. {
  93. name: "测试用户",
  94. rpm: 60,
  95. dailyQuota: 10,
  96. },
  97. USER_TOKEN
  98. );
  99. expect(response.ok).toBe(true);
  100. expect(data.ok).toBe(false);
  101. expect(data.error).toContain("无权限");
  102. });
  103. test("缺少必需参数应返回错误", async () => {
  104. const { response, data } = await callUsersApi("addUser", {
  105. // 缺少 name
  106. rpm: 60,
  107. });
  108. expect(response.ok).toBe(true);
  109. expect(data.ok).toBe(false);
  110. expect(data.error).toBeDefined();
  111. });
  112. test("参数类型错误应返回验证失败", async () => {
  113. const { response, data } = await callUsersApi("addUser", {
  114. name: "测试用户",
  115. rpm: "not-a-number", // 应该是 number
  116. dailyQuota: 10,
  117. });
  118. expect(response.ok).toBe(true);
  119. expect(data.ok).toBe(false);
  120. expect(data.error).toBeDefined();
  121. });
  122. test("RPM 超出范围应返回验证失败", async () => {
  123. const { response, data } = await callUsersApi("addUser", {
  124. name: "测试用户",
  125. rpm: -1, // 应该 >= 1
  126. dailyQuota: 10,
  127. });
  128. expect(response.ok).toBe(true);
  129. expect(data.ok).toBe(false);
  130. expect(data.error).toBeDefined();
  131. });
  132. test("日限额超出范围应返回验证失败", async () => {
  133. const { response, data } = await callUsersApi("addUser", {
  134. name: "测试用户",
  135. rpm: 60,
  136. dailyQuota: -1, // 日限额不能为负数(0 表示无限制)
  137. });
  138. expect(response.ok).toBe(true);
  139. expect(data.ok).toBe(false);
  140. expect(data.error).toBeDefined();
  141. });
  142. test("创建带过期时间的用户", async () => {
  143. const futureDate = new Date();
  144. futureDate.setDate(futureDate.getDate() + 30); // 30 天后过期
  145. const { response, data } = await callUsersApi("addUser", {
  146. name: `测试用户_过期_${Date.now()}`,
  147. rpm: 60,
  148. dailyQuota: 10,
  149. expiresAt: futureDate,
  150. isEnabled: true,
  151. });
  152. expect(response.ok).toBe(true);
  153. expect(data.ok).toBe(true);
  154. expect(data.data.user.expiresAt).toBeDefined();
  155. });
  156. test("创建带限额的用户", async () => {
  157. const { response, data } = await callUsersApi("addUser", {
  158. name: `测试用户_限额_${Date.now()}`,
  159. rpm: 60,
  160. dailyQuota: 10,
  161. limit5hUsd: 5,
  162. limitWeeklyUsd: 20,
  163. limitMonthlyUsd: 50,
  164. limitConcurrentSessions: 3,
  165. });
  166. expect(response.ok).toBe(true);
  167. expect(data.ok).toBe(true);
  168. expect(data.data.user.limit5hUsd).toBe(5);
  169. expect(data.data.user.limitWeeklyUsd).toBe(20);
  170. expect(data.data.user.limitMonthlyUsd).toBe(50);
  171. expect(data.data.user.limitConcurrentSessions).toBe(3);
  172. });
  173. });
  174. describe.skip("用户管理 - 编辑用户 (editUser)", () => {
  175. let testUserId: number;
  176. beforeEach(async () => {
  177. // 创建测试用户
  178. const { data } = await callUsersApi("addUser", {
  179. name: `待编辑用户_${Date.now()}`,
  180. rpm: 60,
  181. dailyQuota: 10,
  182. });
  183. testUserId = data.data?.user?.id;
  184. });
  185. test("应该成功编辑用户", async () => {
  186. const { response, data } = await callUsersApi("editUser", {
  187. userId: testUserId,
  188. name: "已修改的用户名",
  189. rpm: 120,
  190. });
  191. expect(response.ok).toBe(true);
  192. expect(data.ok).toBe(true);
  193. });
  194. test("非管理员不能编辑其他用户", async () => {
  195. const { response, data } = await callUsersApi(
  196. "editUser",
  197. {
  198. userId: testUserId,
  199. name: "尝试修改",
  200. },
  201. USER_TOKEN
  202. );
  203. expect(response.ok).toBe(true);
  204. expect(data.ok).toBe(false);
  205. expect(data.error).toContain("无权限");
  206. });
  207. test("编辑不存在的用户应返回错误", async () => {
  208. const { response, data } = await callUsersApi("editUser", {
  209. userId: 999999,
  210. name: "不存在的用户",
  211. });
  212. expect(response.ok).toBe(true);
  213. expect(data.ok).toBe(false);
  214. });
  215. test("更新用户限额", async () => {
  216. const { response, data } = await callUsersApi("editUser", {
  217. userId: testUserId,
  218. limit5hUsd: 10,
  219. limitWeeklyUsd: 30,
  220. limitMonthlyUsd: 100,
  221. });
  222. expect(response.ok).toBe(true);
  223. expect(data.ok).toBe(true);
  224. });
  225. test("更新用户标签", async () => {
  226. const { response, data } = await callUsersApi("editUser", {
  227. userId: testUserId,
  228. tags: ["测试", "开发"],
  229. });
  230. expect(response.ok).toBe(true);
  231. expect(data.ok).toBe(true);
  232. });
  233. test("更新用户供应商分组", async () => {
  234. const { response, data } = await callUsersApi("editUser", {
  235. userId: testUserId,
  236. providerGroup: "group1,group2",
  237. });
  238. expect(response.ok).toBe(true);
  239. expect(data.ok).toBe(true);
  240. });
  241. });
  242. describe.skip("用户管理 - 删除用户 (removeUser)", () => {
  243. let testUserId: number;
  244. beforeEach(async () => {
  245. // 创建待删除用户
  246. const { data } = await callUsersApi("addUser", {
  247. name: `待删除用户_${Date.now()}`,
  248. rpm: 60,
  249. dailyQuota: 10,
  250. });
  251. testUserId = data.data?.user?.id;
  252. });
  253. test("应该成功删除用户", async () => {
  254. const { response, data } = await callUsersApi("removeUser", {
  255. userId: testUserId,
  256. });
  257. expect(response.ok).toBe(true);
  258. expect(data.ok).toBe(true);
  259. });
  260. test("非管理员不能删除用户", async () => {
  261. const { response, data } = await callUsersApi(
  262. "removeUser",
  263. {
  264. userId: testUserId,
  265. },
  266. USER_TOKEN
  267. );
  268. expect(response.ok).toBe(true);
  269. expect(data.ok).toBe(false);
  270. expect(data.error).toContain("无权限");
  271. });
  272. test("删除不存在的用户应返回错误", async () => {
  273. const { response, data } = await callUsersApi("removeUser", {
  274. userId: 999999,
  275. });
  276. expect(response.ok).toBe(true);
  277. expect(data.ok).toBe(false);
  278. });
  279. });
  280. describe.skip("用户管理 - 启用/禁用用户 (toggleUserEnabled)", () => {
  281. let testUserId: number;
  282. beforeEach(async () => {
  283. // 创建测试用户
  284. const { data } = await callUsersApi("addUser", {
  285. name: `待切换状态用户_${Date.now()}`,
  286. rpm: 60,
  287. dailyQuota: 10,
  288. isEnabled: true,
  289. });
  290. testUserId = data.data?.user?.id;
  291. });
  292. test("应该成功禁用用户", async () => {
  293. const { response, data } = await callUsersApi("toggleUserEnabled", {
  294. userId: testUserId,
  295. enabled: false,
  296. });
  297. expect(response.ok).toBe(true);
  298. expect(data.ok).toBe(true);
  299. });
  300. test("应该成功启用用户", async () => {
  301. const { response, data } = await callUsersApi("toggleUserEnabled", {
  302. userId: testUserId,
  303. enabled: true,
  304. });
  305. expect(response.ok).toBe(true);
  306. expect(data.ok).toBe(true);
  307. });
  308. test("非管理员不能切换用户状态", async () => {
  309. const { response, data } = await callUsersApi(
  310. "toggleUserEnabled",
  311. {
  312. userId: testUserId,
  313. enabled: false,
  314. },
  315. USER_TOKEN
  316. );
  317. expect(response.ok).toBe(true);
  318. expect(data.ok).toBe(false);
  319. expect(data.error).toContain("无权限");
  320. });
  321. test("参数类型错误应返回失败", async () => {
  322. const { response, data } = await callUsersApi("toggleUserEnabled", {
  323. userId: testUserId,
  324. enabled: "not-a-boolean", // 应该是 boolean
  325. });
  326. expect(response.ok).toBe(true);
  327. expect(data.ok).toBe(false);
  328. });
  329. });
  330. describe.skip("用户管理 - 续期用户 (renewUser)", () => {
  331. let testUserId: number;
  332. beforeEach(async () => {
  333. // 创建带过期时间的测试用户
  334. const futureDate = new Date();
  335. futureDate.setDate(futureDate.getDate() + 7);
  336. const { data } = await callUsersApi("addUser", {
  337. name: `待续期用户_${Date.now()}`,
  338. rpm: 60,
  339. dailyQuota: 10,
  340. expiresAt: futureDate,
  341. });
  342. testUserId = data.data?.user?.id;
  343. });
  344. test("应该成功续期用户", async () => {
  345. const newExpiresAt = new Date();
  346. newExpiresAt.setDate(newExpiresAt.getDate() + 30);
  347. const { response, data } = await callUsersApi("renewUser", {
  348. userId: testUserId,
  349. expiresAt: newExpiresAt.toISOString(),
  350. });
  351. expect(response.ok).toBe(true);
  352. expect(data.ok).toBe(true);
  353. });
  354. test("续期时启用用户", async () => {
  355. const newExpiresAt = new Date();
  356. newExpiresAt.setDate(newExpiresAt.getDate() + 30);
  357. const { response, data } = await callUsersApi("renewUser", {
  358. userId: testUserId,
  359. expiresAt: newExpiresAt.toISOString(),
  360. enableUser: true,
  361. });
  362. expect(response.ok).toBe(true);
  363. expect(data.ok).toBe(true);
  364. });
  365. test("非管理员不能续期用户", async () => {
  366. const newExpiresAt = new Date();
  367. newExpiresAt.setDate(newExpiresAt.getDate() + 30);
  368. const { response, data } = await callUsersApi(
  369. "renewUser",
  370. {
  371. userId: testUserId,
  372. expiresAt: newExpiresAt.toISOString(),
  373. },
  374. USER_TOKEN
  375. );
  376. expect(response.ok).toBe(true);
  377. expect(data.ok).toBe(false);
  378. expect(data.error).toContain("无权限");
  379. });
  380. test("过期时间为过去应返回错误", async () => {
  381. const pastDate = new Date();
  382. pastDate.setDate(pastDate.getDate() - 1);
  383. const { response, data } = await callUsersApi("renewUser", {
  384. userId: testUserId,
  385. expiresAt: pastDate.toISOString(),
  386. });
  387. expect(response.ok).toBe(true);
  388. expect(data.ok).toBe(false);
  389. expect(data.error).toBeDefined();
  390. });
  391. test("过期时间超过 10 年应返回错误", async () => {
  392. const farFutureDate = new Date();
  393. farFutureDate.setFullYear(farFutureDate.getFullYear() + 11);
  394. const { response, data } = await callUsersApi("renewUser", {
  395. userId: testUserId,
  396. expiresAt: farFutureDate.toISOString(),
  397. });
  398. expect(response.ok).toBe(true);
  399. expect(data.ok).toBe(false);
  400. expect(data.error).toBeDefined();
  401. });
  402. });
  403. describe.skip("用户管理 - 获取用户限额使用情况 (getUserLimitUsage)", () => {
  404. let testUserId: number;
  405. beforeEach(async () => {
  406. // 创建带限额的测试用户
  407. const { data } = await callUsersApi("addUser", {
  408. name: `限额测试用户_${Date.now()}`,
  409. rpm: 60,
  410. dailyQuota: 10,
  411. limit5hUsd: 5,
  412. limitWeeklyUsd: 20,
  413. limitMonthlyUsd: 50,
  414. });
  415. testUserId = data.data?.user?.id;
  416. });
  417. test("应该成功获取用户限额使用情况", async () => {
  418. const { response, data } = await callUsersApi("getUserLimitUsage", {
  419. userId: testUserId,
  420. });
  421. expect(response.ok).toBe(true);
  422. expect(data.ok).toBe(true);
  423. expect(data.data).toBeDefined();
  424. expect(data.data.rpm).toBeDefined();
  425. expect(data.data.dailyCost).toBeDefined();
  426. expect(data.data.rpm.limit).toBe(60);
  427. expect(data.data.dailyCost.limit).toBe(10);
  428. });
  429. test("非管理员不能查看其他用户的限额", async () => {
  430. const { response, data } = await callUsersApi(
  431. "getUserLimitUsage",
  432. {
  433. userId: testUserId,
  434. },
  435. USER_TOKEN
  436. );
  437. expect(response.ok).toBe(true);
  438. expect(data.ok).toBe(false);
  439. expect(data.error).toContain("无权限");
  440. });
  441. test("查询不存在的用户应返回错误", async () => {
  442. const { response, data } = await callUsersApi("getUserLimitUsage", {
  443. userId: 999999,
  444. });
  445. expect(response.ok).toBe(true);
  446. expect(data.ok).toBe(false);
  447. expect(data.error).toBeDefined();
  448. });
  449. });
  450. describe.skip("用户管理 - 响应格式验证", () => {
  451. test("所有成功响应应符合 ActionResult 格式", async () => {
  452. const { response, data } = await callUsersApi("addUser", {
  453. name: `格式验证用户_${Date.now()}`,
  454. rpm: 60,
  455. dailyQuota: 10,
  456. });
  457. expect(response.ok).toBe(true);
  458. expect(data).toHaveProperty("ok");
  459. expect(data.ok).toBe(true);
  460. expect(data).toHaveProperty("data");
  461. });
  462. test("所有错误响应应符合 ActionResult 格式", async () => {
  463. const { response, data } = await callUsersApi(
  464. "addUser",
  465. {
  466. name: "测试用户",
  467. rpm: 60,
  468. },
  469. USER_TOKEN
  470. );
  471. expect(response.ok).toBe(true);
  472. expect(data).toHaveProperty("ok");
  473. expect(data.ok).toBe(false);
  474. expect(data).toHaveProperty("error");
  475. expect(typeof data.error).toBe("string");
  476. });
  477. });