| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- /**
- * 用户管理模块 API 测试
- *
- * ⚠️ 状态:待重构为集成测试
- *
- * 当前问题:
- * - 这些测试试图在单元测试环境中运行需要完整 Next.js 运行时的 Server Actions
- * - Server Actions 使用了 cookies()、getTranslations()、revalidatePath() 等 Next.js 特定 API
- * - 这些 API 需要 Next.js 的请求上下文(AsyncLocalStorage),在 Vitest 环境无法提供
- *
- * 解决方案:
- * - 方案 A(推荐):重写为集成测试 - 启动真实的 Next.js 服务器,通过 HTTP 请求测试
- * - 方案 B:单元测试 Repository 层 - 只测试不依赖 Next.js 运行时的纯业务逻辑
- *
- * 详见:tests/DIAGNOSIS-FINAL.md
- *
- * ---
- *
- * 测试范围:
- * - getUsers() - 获取用户列表
- * - addUser() - 创建用户
- * - editUser() - 编辑用户
- * - removeUser() - 删除用户
- * - toggleUserEnabled() - 启用/禁用用户
- * - renewUser() - 续期用户
- * - getUserLimitUsage() - 获取用户限额使用情况
- *
- * 测试场景:
- * - 正常场景(CRUD 操作)
- * - 参数验证(边界值、非法值)
- * - 权限控制(管理员 vs 普通用户)
- * - 错误处理
- */
- import { beforeEach, describe, expect, test } from "vitest";
- import { callActionsRoute } from "../test-utils";
- // 测试用管理员 Token(实际使用时需要有效的 token)
- const ADMIN_TOKEN = process.env.TEST_ADMIN_TOKEN || "test-admin-token";
- const USER_TOKEN = "test-user-token";
- // 辅助函数:调用用户管理 API
- async function callUsersApi(
- action: string,
- body: Record<string, unknown> = {},
- authToken = ADMIN_TOKEN
- ) {
- const { response, json } = await callActionsRoute({
- method: "POST",
- pathname: `/api/actions/users/${action}`,
- authToken,
- body,
- });
- return { response, data: json as any };
- }
- // ⚠️ 跳过所有测试直到重构为集成测试
- describe.skip("用户管理 - API 测试(待重构)", () => {
- test("未登录应返回空数组", async () => {
- const { response, data } = await callUsersApi("getUsers", {}, undefined);
- expect(response.ok).toBe(true);
- expect(Array.isArray(data)).toBe(true);
- expect(data.length).toBe(0);
- });
- test("管理员应该可以查看所有用户", async () => {
- const { response, data } = await callUsersApi("getUsers");
- expect(response.ok).toBe(true);
- expect(Array.isArray(data)).toBe(true);
- });
- test("普通用户只能看到自己", async () => {
- const { response, data } = await callUsersApi("getUsers", {}, USER_TOKEN);
- expect(response.ok).toBe(true);
- // 由于测试环境的 USER_TOKEN 无法查询到真实用户,此处会返回空数组
- expect(Array.isArray(data)).toBe(true);
- });
- });
- describe.skip("用户管理 - 创建用户 (addUser)", () => {
- test("应该成功创建用户", async () => {
- const { response, data } = await callUsersApi("addUser", {
- name: `测试用户_${Date.now()}`,
- note: "API 测试创建的用户",
- rpm: 60,
- dailyQuota: 10,
- isEnabled: true,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- expect(data.data).toBeDefined();
- expect(data.data.user).toBeDefined();
- expect(data.data.defaultKey).toBeDefined();
- expect(data.data.defaultKey.key).toMatch(/^sk-[a-f0-9]{32}$/);
- });
- test("非管理员不能创建用户", async () => {
- const { response, data } = await callUsersApi(
- "addUser",
- {
- name: "测试用户",
- rpm: 60,
- dailyQuota: 10,
- },
- USER_TOKEN
- );
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toContain("无权限");
- });
- test("缺少必需参数应返回错误", async () => {
- const { response, data } = await callUsersApi("addUser", {
- // 缺少 name
- rpm: 60,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toBeDefined();
- });
- test("参数类型错误应返回验证失败", async () => {
- const { response, data } = await callUsersApi("addUser", {
- name: "测试用户",
- rpm: "not-a-number", // 应该是 number
- dailyQuota: 10,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toBeDefined();
- });
- test("RPM 超出范围应返回验证失败", async () => {
- const { response, data } = await callUsersApi("addUser", {
- name: "测试用户",
- rpm: -1, // 应该 >= 1
- dailyQuota: 10,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toBeDefined();
- });
- test("日限额超出范围应返回验证失败", async () => {
- const { response, data } = await callUsersApi("addUser", {
- name: "测试用户",
- rpm: 60,
- dailyQuota: -1, // 日限额不能为负数(0 表示无限制)
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toBeDefined();
- });
- test("创建带过期时间的用户", async () => {
- const futureDate = new Date();
- futureDate.setDate(futureDate.getDate() + 30); // 30 天后过期
- const { response, data } = await callUsersApi("addUser", {
- name: `测试用户_过期_${Date.now()}`,
- rpm: 60,
- dailyQuota: 10,
- expiresAt: futureDate,
- isEnabled: true,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- expect(data.data.user.expiresAt).toBeDefined();
- });
- test("创建带限额的用户", async () => {
- const { response, data } = await callUsersApi("addUser", {
- name: `测试用户_限额_${Date.now()}`,
- rpm: 60,
- dailyQuota: 10,
- limit5hUsd: 5,
- limitWeeklyUsd: 20,
- limitMonthlyUsd: 50,
- limitConcurrentSessions: 3,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- expect(data.data.user.limit5hUsd).toBe(5);
- expect(data.data.user.limitWeeklyUsd).toBe(20);
- expect(data.data.user.limitMonthlyUsd).toBe(50);
- expect(data.data.user.limitConcurrentSessions).toBe(3);
- });
- });
- describe.skip("用户管理 - 编辑用户 (editUser)", () => {
- let testUserId: number;
- beforeEach(async () => {
- // 创建测试用户
- const { data } = await callUsersApi("addUser", {
- name: `待编辑用户_${Date.now()}`,
- rpm: 60,
- dailyQuota: 10,
- });
- testUserId = data.data?.user?.id;
- });
- test("应该成功编辑用户", async () => {
- const { response, data } = await callUsersApi("editUser", {
- userId: testUserId,
- name: "已修改的用户名",
- rpm: 120,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- });
- test("非管理员不能编辑其他用户", async () => {
- const { response, data } = await callUsersApi(
- "editUser",
- {
- userId: testUserId,
- name: "尝试修改",
- },
- USER_TOKEN
- );
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toContain("无权限");
- });
- test("编辑不存在的用户应返回错误", async () => {
- const { response, data } = await callUsersApi("editUser", {
- userId: 999999,
- name: "不存在的用户",
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- });
- test("更新用户限额", async () => {
- const { response, data } = await callUsersApi("editUser", {
- userId: testUserId,
- limit5hUsd: 10,
- limitWeeklyUsd: 30,
- limitMonthlyUsd: 100,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- });
- test("更新用户标签", async () => {
- const { response, data } = await callUsersApi("editUser", {
- userId: testUserId,
- tags: ["测试", "开发"],
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- });
- test("更新用户供应商分组", async () => {
- const { response, data } = await callUsersApi("editUser", {
- userId: testUserId,
- providerGroup: "group1,group2",
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- });
- });
- describe.skip("用户管理 - 删除用户 (removeUser)", () => {
- let testUserId: number;
- beforeEach(async () => {
- // 创建待删除用户
- const { data } = await callUsersApi("addUser", {
- name: `待删除用户_${Date.now()}`,
- rpm: 60,
- dailyQuota: 10,
- });
- testUserId = data.data?.user?.id;
- });
- test("应该成功删除用户", async () => {
- const { response, data } = await callUsersApi("removeUser", {
- userId: testUserId,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- });
- test("非管理员不能删除用户", async () => {
- const { response, data } = await callUsersApi(
- "removeUser",
- {
- userId: testUserId,
- },
- USER_TOKEN
- );
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toContain("无权限");
- });
- test("删除不存在的用户应返回错误", async () => {
- const { response, data } = await callUsersApi("removeUser", {
- userId: 999999,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- });
- });
- describe.skip("用户管理 - 启用/禁用用户 (toggleUserEnabled)", () => {
- let testUserId: number;
- beforeEach(async () => {
- // 创建测试用户
- const { data } = await callUsersApi("addUser", {
- name: `待切换状态用户_${Date.now()}`,
- rpm: 60,
- dailyQuota: 10,
- isEnabled: true,
- });
- testUserId = data.data?.user?.id;
- });
- test("应该成功禁用用户", async () => {
- const { response, data } = await callUsersApi("toggleUserEnabled", {
- userId: testUserId,
- enabled: false,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- });
- test("应该成功启用用户", async () => {
- const { response, data } = await callUsersApi("toggleUserEnabled", {
- userId: testUserId,
- enabled: true,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- });
- test("非管理员不能切换用户状态", async () => {
- const { response, data } = await callUsersApi(
- "toggleUserEnabled",
- {
- userId: testUserId,
- enabled: false,
- },
- USER_TOKEN
- );
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toContain("无权限");
- });
- test("参数类型错误应返回失败", async () => {
- const { response, data } = await callUsersApi("toggleUserEnabled", {
- userId: testUserId,
- enabled: "not-a-boolean", // 应该是 boolean
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- });
- });
- describe.skip("用户管理 - 续期用户 (renewUser)", () => {
- let testUserId: number;
- beforeEach(async () => {
- // 创建带过期时间的测试用户
- const futureDate = new Date();
- futureDate.setDate(futureDate.getDate() + 7);
- const { data } = await callUsersApi("addUser", {
- name: `待续期用户_${Date.now()}`,
- rpm: 60,
- dailyQuota: 10,
- expiresAt: futureDate,
- });
- testUserId = data.data?.user?.id;
- });
- test("应该成功续期用户", async () => {
- const newExpiresAt = new Date();
- newExpiresAt.setDate(newExpiresAt.getDate() + 30);
- const { response, data } = await callUsersApi("renewUser", {
- userId: testUserId,
- expiresAt: newExpiresAt.toISOString(),
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- });
- test("续期时启用用户", async () => {
- const newExpiresAt = new Date();
- newExpiresAt.setDate(newExpiresAt.getDate() + 30);
- const { response, data } = await callUsersApi("renewUser", {
- userId: testUserId,
- expiresAt: newExpiresAt.toISOString(),
- enableUser: true,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- });
- test("非管理员不能续期用户", async () => {
- const newExpiresAt = new Date();
- newExpiresAt.setDate(newExpiresAt.getDate() + 30);
- const { response, data } = await callUsersApi(
- "renewUser",
- {
- userId: testUserId,
- expiresAt: newExpiresAt.toISOString(),
- },
- USER_TOKEN
- );
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toContain("无权限");
- });
- test("过期时间为过去应返回错误", async () => {
- const pastDate = new Date();
- pastDate.setDate(pastDate.getDate() - 1);
- const { response, data } = await callUsersApi("renewUser", {
- userId: testUserId,
- expiresAt: pastDate.toISOString(),
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toBeDefined();
- });
- test("过期时间超过 10 年应返回错误", async () => {
- const farFutureDate = new Date();
- farFutureDate.setFullYear(farFutureDate.getFullYear() + 11);
- const { response, data } = await callUsersApi("renewUser", {
- userId: testUserId,
- expiresAt: farFutureDate.toISOString(),
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toBeDefined();
- });
- });
- describe.skip("用户管理 - 获取用户限额使用情况 (getUserLimitUsage)", () => {
- let testUserId: number;
- beforeEach(async () => {
- // 创建带限额的测试用户
- const { data } = await callUsersApi("addUser", {
- name: `限额测试用户_${Date.now()}`,
- rpm: 60,
- dailyQuota: 10,
- limit5hUsd: 5,
- limitWeeklyUsd: 20,
- limitMonthlyUsd: 50,
- });
- testUserId = data.data?.user?.id;
- });
- test("应该成功获取用户限额使用情况", async () => {
- const { response, data } = await callUsersApi("getUserLimitUsage", {
- userId: testUserId,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(true);
- expect(data.data).toBeDefined();
- expect(data.data.rpm).toBeDefined();
- expect(data.data.dailyCost).toBeDefined();
- expect(data.data.rpm.limit).toBe(60);
- expect(data.data.dailyCost.limit).toBe(10);
- });
- test("非管理员不能查看其他用户的限额", async () => {
- const { response, data } = await callUsersApi(
- "getUserLimitUsage",
- {
- userId: testUserId,
- },
- USER_TOKEN
- );
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toContain("无权限");
- });
- test("查询不存在的用户应返回错误", async () => {
- const { response, data } = await callUsersApi("getUserLimitUsage", {
- userId: 999999,
- });
- expect(response.ok).toBe(true);
- expect(data.ok).toBe(false);
- expect(data.error).toBeDefined();
- });
- });
- describe.skip("用户管理 - 响应格式验证", () => {
- test("所有成功响应应符合 ActionResult 格式", async () => {
- const { response, data } = await callUsersApi("addUser", {
- name: `格式验证用户_${Date.now()}`,
- rpm: 60,
- dailyQuota: 10,
- });
- expect(response.ok).toBe(true);
- expect(data).toHaveProperty("ok");
- expect(data.ok).toBe(true);
- expect(data).toHaveProperty("data");
- });
- test("所有错误响应应符合 ActionResult 格式", async () => {
- const { response, data } = await callUsersApi(
- "addUser",
- {
- name: "测试用户",
- rpm: 60,
- },
- USER_TOKEN
- );
- expect(response.ok).toBe(true);
- expect(data).toHaveProperty("ok");
- expect(data.ok).toBe(false);
- expect(data).toHaveProperty("error");
- expect(typeof data.error).toBe("string");
- });
- });
|