- tests/01: add graceful login timeout handling with try/catch - tests/02: migrate from isolated request.newContext to uiLogin + page.request pattern - tests/03: workspace members, platform users CRUD, billing, alert-config - tests/04: room messages, room list, repo list/detail APIs - tests/05: project members CRUD, project detail, project billing - tests/06: API token CRUD, logout, health check - tests/07: AI provider/model/version/pricing CRUD - tests/08: admin user CRUD, role CRUD All tests use consistent checkBackendAvailable() + uiLogin() pattern with graceful degradation (test.skip) when backend is unreachable.
251 lines
8.9 KiB
TypeScript
251 lines
8.9 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
|
|
const ADMIN_USER = process.env.ADMIN_TEST_USERNAME || "admin";
|
|
const ADMIN_PASS = process.env.ADMIN_TEST_PASSWORD || "admin123";
|
|
|
|
async function checkBackendAvailable(): Promise<boolean> {
|
|
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<typeof test>[0]): Promise<boolean> {
|
|
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("Admin 用户 CRUD API", () => {
|
|
test("GET /api/users/[id] 返回用户详情(含角色)", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const randomUser = `testuser_${Date.now()}`;
|
|
const createRes = await page.request.post("/api/users", {
|
|
data: { username: randomUser, password: "TestPass123!", roleIds: [] },
|
|
});
|
|
if (createRes.status() > 201) { test.skip(); }
|
|
const created = await createRes.json();
|
|
const userId = created.user?.id;
|
|
if (!userId) { test.skip(); }
|
|
|
|
const res = await page.request.get(`/api/users/${userId}`);
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(data).toHaveProperty("username");
|
|
expect(data).toHaveProperty("roles");
|
|
expect(Array.isArray(data.roles)).toBe(true);
|
|
expect(data).not.toHaveProperty("password_hash");
|
|
|
|
await page.request.delete(`/api/users/${userId}`);
|
|
});
|
|
|
|
test("GET /api/users/[id] 不存在的用户返回404", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.get("/api/users/999999");
|
|
expect(res.status()).toBe(404);
|
|
});
|
|
|
|
test("PUT /api/users/[id] 更新用户密码和状态", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const randomUser = `testuser_${Date.now()}`;
|
|
const createRes = await page.request.post("/api/users", {
|
|
data: { username: randomUser, password: "TestPass123!", roleIds: [] },
|
|
});
|
|
if (createRes.status() > 201) { test.skip(); }
|
|
const created = await createRes.json();
|
|
const userId = created.user?.id;
|
|
if (!userId) { test.skip(); }
|
|
|
|
const res = await page.request.put(`/api/users/${userId}`, {
|
|
data: { password: "NewPass123!", isActive: true },
|
|
});
|
|
expect(res.status()).toBe(200);
|
|
|
|
await page.request.delete(`/api/users/${userId}`);
|
|
});
|
|
|
|
test("PUT /api/users/[id] 更新用户角色", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const rolesRes = await page.request.get("/api/roles");
|
|
const roles = (await rolesRes.json()).roles || [];
|
|
if (!roles.length) { test.skip(); }
|
|
const roleId = roles[0].id;
|
|
|
|
const randomUser = `testuser_${Date.now()}`;
|
|
const createRes = await page.request.post("/api/users", {
|
|
data: { username: randomUser, password: "TestPass123!", roleIds: [] },
|
|
});
|
|
if (createRes.status() > 201) { test.skip(); }
|
|
const created = await createRes.json();
|
|
const userId = created.user?.id;
|
|
if (!userId) { test.skip(); }
|
|
|
|
const res = await page.request.put(`/api/users/${userId}`, {
|
|
data: { roleIds: [roleId] },
|
|
});
|
|
expect(res.status()).toBe(200);
|
|
|
|
await page.request.delete(`/api/users/${userId}`);
|
|
});
|
|
|
|
test("PUT /api/users/[id] 不存在的用户返回404", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.put("/api/users/999999", {
|
|
data: { isActive: false },
|
|
});
|
|
expect(res.status()).toBe(404);
|
|
});
|
|
|
|
test("DELETE /api/users/[id] 删除用户", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const randomUser = `testuser_${Date.now()}`;
|
|
const createRes = await page.request.post("/api/users", {
|
|
data: { username: randomUser, password: "TestPass123!", roleIds: [] },
|
|
});
|
|
if (createRes.status() > 201) { test.skip(); }
|
|
const created = await createRes.json();
|
|
const userId = created.user?.id;
|
|
if (!userId) { test.skip(); }
|
|
|
|
const res = await page.request.delete(`/api/users/${userId}`);
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(data.success).toBe(true);
|
|
});
|
|
|
|
test("DELETE /api/users/[id] 不存在的用户返回404", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.delete("/api/users/999999");
|
|
expect(res.status()).toBe(404);
|
|
});
|
|
|
|
test("DELETE /api/users/1 禁止删除超级管理员", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.delete("/api/users/1");
|
|
expect(res.status()).toBe(400);
|
|
});
|
|
});
|
|
|
|
test.describe("Admin 角色 CRUD API", () => {
|
|
test("GET /api/roles/[id] 返回角色详情(含权限)", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const rolesRes = await page.request.get("/api/roles");
|
|
const roles = (await rolesRes.json()).roles || [];
|
|
if (!roles.length) { test.skip(); }
|
|
const roleId = roles[0].id;
|
|
|
|
const res = await page.request.get(`/api/roles/${roleId}`);
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(data).toHaveProperty("name");
|
|
expect(data).toHaveProperty("description");
|
|
});
|
|
|
|
test("GET /api/roles/[id] 不存在的角色返回404", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.get("/api/roles/999999");
|
|
expect(res.status()).toBe(404);
|
|
});
|
|
|
|
test("PUT /api/roles/[id] 更新角色", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const rolesRes = await page.request.get("/api/roles");
|
|
const roles = (await rolesRes.json()).roles || [];
|
|
const testRole = roles.find((r: { name: string }) => r.name !== "超级管理员");
|
|
if (!testRole) { test.skip(); }
|
|
|
|
const res = await page.request.put(`/api/roles/${testRole.id}`, {
|
|
data: { name: `${testRole.name}_updated`, description: "Updated description" },
|
|
});
|
|
expect(res.status()).toBe(200);
|
|
});
|
|
|
|
test("PUT /api/roles/[id] 更新角色权限", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const rolesRes = await page.request.get("/api/roles");
|
|
const roles = (await rolesRes.json()).roles || [];
|
|
const permsRes = await page.request.get("/api/permissions");
|
|
const perms = (await permsRes.json()).permissions || [];
|
|
if (!roles.length || !perms.length) { test.skip(); }
|
|
|
|
const testRole = roles.find((r: { name: string }) => r.name !== "超级管理员");
|
|
if (!testRole) { test.skip(); }
|
|
|
|
const res = await page.request.put(`/api/roles/${testRole.id}`, {
|
|
data: { permissionIds: [perms[0].id] },
|
|
});
|
|
expect(res.status()).toBe(200);
|
|
});
|
|
|
|
test("PUT /api/roles/[id] 不存在的角色返回404", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.put("/api/roles/999999", {
|
|
data: { name: "Updated" },
|
|
});
|
|
expect(res.status()).toBe(404);
|
|
});
|
|
|
|
test("DELETE /api/roles/[id] 删除角色", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const randomRole = `testrole_${Date.now()}`;
|
|
const createRes = await page.request.post("/api/roles", {
|
|
data: { name: randomRole, description: "Test role for CRUD" },
|
|
});
|
|
if (createRes.status() > 201) { test.skip(); }
|
|
const created = await createRes.json();
|
|
const roleId = created.id;
|
|
if (!roleId) { test.skip(); }
|
|
|
|
const res = await page.request.delete(`/api/roles/${roleId}`);
|
|
expect([200, 404]).toContain(res.status());
|
|
});
|
|
|
|
test("DELETE /api/roles/1 禁止删除超级管理员角色", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.delete("/api/roles/1");
|
|
expect(res.status()).toBe(400);
|
|
});
|
|
});
|