import { test, expect } from "@playwright/test"; const ADMIN_USER = process.env.ADMIN_TEST_USERNAME || "admin"; const ADMIN_PASS = process.env.ADMIN_TEST_PASSWORD || "admin123"; /** * 快速检测后端是否可达(2秒超时)。 */ async function checkBackendAvailable(): Promise { try { const ctrl = new AbortController(); const id = setTimeout(() => ctrl.abort(), 2000); const res = await fetch("http://localhost:3001/api/health", { signal: ctrl.signal }); clearTimeout(id); return res.ok; } catch { return false; } } async function uiLogin(page: Parameters[0]): Promise { try { await page.goto("/login"); await page.fill("input#username", ADMIN_USER); await page.fill("input#password", ADMIN_PASS); await page.click('button[type="submit"]'); await page.waitForURL((url) => !url.toString().includes("/login"), { timeout: 8000 }); return true; } catch { return false; } } test.describe("Workspace 成员管理 API", () => { test("GET /api/platform/workspaces/[id]/members 返回成员列表", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const res = await page.request.get(`/api/platform/workspaces/${workspaceId}/members`); expect(res.status()).toBe(200); const data = await res.json(); expect(Array.isArray(data.members)).toBe(true); }); test("POST /api/platform/workspaces/[id]/members 添加成员", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const usersRes = await page.request.get("/api/platform/users?pageSize=5"); const usersData = await usersRes.json(); const membersRes = await page.request.get(`/api/platform/workspaces/${workspaceId}/members`); const members = (await membersRes.json()).members || []; const memberIds = members.map((m: { userId: string }) => m.userId); const testUser = usersData.users?.find((u: { uid: string }) => !memberIds.includes(u.uid)); if (!testUser) { test.skip(); } const res = await page.request.post(`/api/platform/workspaces/${workspaceId}/members`, { data: { userId: testUser.uid, role: "member" }, }); expect([201, 409]).toContain(res.status()); }); test("POST /api/platform/workspaces/[id]/members 缺少 userId 返回 400", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const res = await page.request.post(`/api/platform/workspaces/${workspaceId}/members`, { data: { role: "member" }, }); expect(res.status()).toBe(400); }); test("POST /api/platform/workspaces/[id]/members 无效角色返回 400", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const usersRes = await page.request.get("/api/platform/users?pageSize=5"); const usersData = await usersRes.json(); const testUser = usersData.users?.[0]; if (!testUser) { test.skip(); } const res = await page.request.post(`/api/platform/workspaces/${workspaceId}/members`, { data: { userId: testUser.uid, role: "invalid_role" }, }); expect(res.status()).toBe(400); }); test("POST /api/platform/workspaces/[id]/add-credit 充值成功", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const res = await page.request.post(`/api/platform/workspaces/${workspaceId}/add-credit`, { data: { amount: 0.01, description: "Test credit" }, }); expect(res.status()).toBe(200); expect((await res.json()).success).toBe(true); }); test("POST /api/platform/workspaces/[id]/add-credit 金额为0返回400", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const res = await page.request.post(`/api/platform/workspaces/${workspaceId}/add-credit`, { data: { amount: 0 }, }); expect(res.status()).toBe(400); }); test("POST /api/platform/workspaces/[id]/add-credit 负金额返回400", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const res = await page.request.post(`/api/platform/workspaces/${workspaceId}/add-credit`, { data: { amount: -10 }, }); expect(res.status()).toBe(400); }); test("GET /api/platform/workspaces/[id]/alert-config 获取告警配置", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const res = await page.request.get(`/api/platform/workspaces/${workspaceId}/alert-config`); expect([200, 404]).toContain(res.status()); }); }); test.describe("Workspace 成员更新/删除 API", () => { test("PATCH /api/platform/workspaces/[id]/members/[memberId] 更新成员角色", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const membersRes = await page.request.get(`/api/platform/workspaces/${workspaceId}/members`); const members = (await membersRes.json()).members || []; const nonOwner = members.find((m: { role: string }) => m.role !== "owner"); if (!nonOwner) { test.skip(); } const res = await page.request.patch( `/api/platform/workspaces/${workspaceId}/members/${nonOwner.id}`, { data: { role: "admin" } } ); expect([200, 403]).toContain(res.status()); }); test("PATCH /api/platform/workspaces/[id]/members/[memberId] 无效角色返回400", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const membersRes = await page.request.get(`/api/platform/workspaces/${workspaceId}/members`); const members = (await membersRes.json()).members || []; const nonOwner = members.find((m: { role: string }) => m.role !== "owner"); if (!nonOwner) { test.skip(); } const res = await page.request.patch( `/api/platform/workspaces/${workspaceId}/members/${nonOwner.id}`, { data: { role: "superadmin" } } ); expect(res.status()).toBe(400); }); test("PATCH /api/platform/workspaces/[id]/members/[memberId] 不存在的成员返回404", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const res = await page.request.patch( `/api/platform/workspaces/${workspaceId}/members/nonexistent`, { data: { role: "admin" } } ); expect(res.status()).toBe(404); }); test("DELETE /api/platform/workspaces/[id]/members/[memberId] 删除非owner成员", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const usersRes = await page.request.get("/api/platform/users?pageSize=5"); const usersData = await usersRes.json(); const membersRes = await page.request.get(`/api/platform/workspaces/${workspaceId}/members`); const members = (await membersRes.json()).members || []; const memberIds = members.map((m: { userId: string }) => m.userId); const testUser = usersData.users?.find((u: { uid: string }) => !memberIds.includes(u.uid)); if (!testUser) { test.skip(); } const addRes = await page.request.post(`/api/platform/workspaces/${workspaceId}/members`, { data: { userId: testUser.uid, role: "member" }, }); if (addRes.status() !== 201) { test.skip(); } const updatedMembers = ( await (await page.request.get(`/api/platform/workspaces/${workspaceId}/members`)).json() ).members || []; const added = updatedMembers.find((m: { userId: string }) => m.userId === testUser.uid); if (!added) { test.skip(); } const res = await page.request.delete( `/api/platform/workspaces/${workspaceId}/members/${added.id}` ); expect([200, 403]).toContain(res.status()); }); test("DELETE /api/platform/workspaces/[id]/members/[memberId] 不存在的成员返回404", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const wsRes = await page.request.get("/api/platform/workspaces?pageSize=1"); const wsData = await wsRes.json(); const workspaceId = wsData.workspaces?.[0]?.id; if (!workspaceId) { test.skip(); } const res = await page.request.delete( `/api/platform/workspaces/${workspaceId}/members/nonexistent` ); expect(res.status()).toBe(404); }); }); test.describe("平台用户管理 API", () => { test("GET /api/platform/users/[uid] 返回用户详情", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const usersRes = await page.request.get("/api/platform/users?pageSize=1"); const usersData = await usersRes.json(); const uid = usersData.users?.[0]?.uid; if (!uid) { test.skip(); } const res = await page.request.get(`/api/platform/users/${uid}`); expect(res.status()).toBe(200); expect((await res.json()).user).toBeDefined(); }); test("GET /api/platform/users/[uid] 不存在的用户返回404", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const res = await page.request.get("/api/platform/users/nonexistent-uid-12345"); expect(res.status()).toBe(404); }); test("PATCH /api/platform/users/[uid] 更新用户信息", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const usersRes = await page.request.get("/api/platform/users?pageSize=1"); const usersData = await usersRes.json(); const uid = usersData.users?.[0]?.uid; if (!uid) { test.skip(); } const res = await page.request.patch(`/api/platform/users/${uid}`, { data: { displayName: "Updated Name" }, }); expect(res.status()).toBe(200); }); test("PATCH /api/platform/users/[uid] 更新邮箱", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const usersRes = await page.request.get("/api/platform/users?pageSize=1"); const usersData = await usersRes.json(); const uid = usersData.users?.[0]?.uid; if (!uid) { test.skip(); } const res = await page.request.patch(`/api/platform/users/${uid}`, { data: { email: "test@example.com" }, }); expect([200, 500]).toContain(res.status()); }); test("PATCH /api/platform/users/[uid] 密码太短返回400", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const usersRes = await page.request.get("/api/platform/users?pageSize=1"); const usersData = await usersRes.json(); const uid = usersData.users?.[0]?.uid; if (!uid) { test.skip(); } const res = await page.request.patch(`/api/platform/users/${uid}`, { data: { password: "123" }, }); expect(res.status()).toBe(400); }); test("PATCH /api/platform/users/[uid] 不存在的用户返回404", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const res = await page.request.patch("/api/platform/users/nonexistent-uid-12345", { data: { displayName: "Test" }, }); expect(res.status()).toBe(404); }); test("PATCH /api/platform/users 批量启用/禁用用户", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const usersRes = await page.request.get("/api/platform/users?pageSize=1"); const usersData = await usersRes.json(); const uid = usersData.users?.[0]?.uid; if (!uid) { test.skip(); } const res = await page.request.patch("/api/platform/users", { data: { ids: [uid], action: "disable" }, }); expect(res.status()).toBe(200); expect((await res.json()).success).toBe(true); }); test("PATCH /api/platform/users 空ids返回400", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const res = await page.request.patch("/api/platform/users", { data: { ids: [], action: "enable" }, }); expect(res.status()).toBe(400); }); test("PATCH /api/platform/users 无效action返回400", async ({ page }) => { if (!await checkBackendAvailable()) { test.skip(); } if (!await uiLogin(page)) { test.skip(); } const usersRes = await page.request.get("/api/platform/users?pageSize=1"); const usersData = await usersRes.json(); const uid = usersData.users?.[0]?.uid; if (!uid) { test.skip(); } const res = await page.request.patch("/api/platform/users", { data: { ids: [uid], action: "toggle" }, }); expect(res.status()).toBe(400); }); });