- 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.
227 lines
8.1 KiB
TypeScript
227 lines
8.1 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("平台房间消息 API", () => {
|
|
test("GET /api/platform/rooms/[id]/messages 返回消息列表", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const roomsRes = await page.request.get("/api/platform/rooms?pageSize=1");
|
|
const roomsData = await roomsRes.json();
|
|
const roomId = roomsData.rooms?.[0]?.id;
|
|
if (!roomId) { test.skip(); }
|
|
|
|
const res = await page.request.get(`/api/platform/rooms/${roomId}/messages`);
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(Array.isArray(data.messages)).toBe(true);
|
|
expect(data).toHaveProperty("total");
|
|
expect(data).toHaveProperty("page");
|
|
expect(data).toHaveProperty("pageSize");
|
|
});
|
|
|
|
test("GET /api/platform/rooms/[id]/messages 支持分页", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const roomsRes = await page.request.get("/api/platform/rooms?pageSize=1");
|
|
const roomsData = await roomsRes.json();
|
|
const roomId = roomsData.rooms?.[0]?.id;
|
|
if (!roomId) { test.skip(); }
|
|
|
|
const res = await page.request.get(`/api/platform/rooms/${roomId}/messages?page=1&pageSize=5`);
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(data.messages.length).toBeLessThanOrEqual(5);
|
|
});
|
|
|
|
test("GET /api/platform/rooms/[id]/messages 不存在的房间返回空列表", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.get("/api/platform/rooms/nonexistent-room-id/messages");
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(Array.isArray(data.messages)).toBe(true);
|
|
});
|
|
|
|
test("DELETE /api/platform/rooms/[id]/messages/[msgId] 撤回消息", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const roomsRes = await page.request.get("/api/platform/rooms?pageSize=1");
|
|
const roomsData = await roomsRes.json();
|
|
const roomId = roomsData.rooms?.[0]?.id;
|
|
if (!roomId) { test.skip(); }
|
|
|
|
const msgRes = await page.request.get(`/api/platform/rooms/${roomId}/messages?pageSize=1`);
|
|
const msgData = await msgRes.json();
|
|
const msgId = msgData.messages?.[0]?.id;
|
|
if (!msgId) { test.skip(); }
|
|
|
|
const res = await page.request.delete(`/api/platform/rooms/${roomId}/messages/${msgId}`);
|
|
expect([200, 404]).toContain(res.status());
|
|
if (res.status() === 200) {
|
|
const data = await res.json();
|
|
expect(data.success).toBe(true);
|
|
}
|
|
});
|
|
|
|
test("DELETE /api/platform/rooms/[id]/messages/[msgId] 不存在的消息返回404", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const roomsRes = await page.request.get("/api/platform/rooms?pageSize=1");
|
|
const roomsData = await roomsRes.json();
|
|
const roomId = roomsData.rooms?.[0]?.id;
|
|
if (!roomId) { test.skip(); }
|
|
|
|
const res = await page.request.delete(`/api/platform/rooms/${roomId}/messages/nonexistent-msg-id`);
|
|
expect(res.status()).toBe(404);
|
|
});
|
|
});
|
|
|
|
test.describe("平台房间列表 API", () => {
|
|
test("GET /api/platform/rooms 支持分页", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.get("/api/platform/rooms?page=1&pageSize=5");
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(Array.isArray(data.rooms)).toBe(true);
|
|
expect(data.rooms.length).toBeLessThanOrEqual(5);
|
|
expect(data).toHaveProperty("total");
|
|
});
|
|
|
|
test("GET /api/platform/rooms 支持搜索", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.get("/api/platform/rooms?search=general");
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(Array.isArray(data.rooms)).toBe(true);
|
|
});
|
|
|
|
test("GET /api/platform/rooms 支持按项目筛选", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const roomsRes = await page.request.get("/api/platform/rooms?pageSize=1");
|
|
const roomsData = await roomsRes.json();
|
|
const projectId = roomsData.rooms?.[0]?.projectId;
|
|
if (!projectId) { test.skip(); }
|
|
|
|
const res = await page.request.get(`/api/platform/rooms?projectId=${projectId}`);
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(Array.isArray(data.rooms)).toBe(true);
|
|
});
|
|
});
|
|
|
|
test.describe("平台仓库 API", () => {
|
|
test("GET /api/platform/repos 支持分页", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.get("/api/platform/repos?page=1&pageSize=5");
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(Array.isArray(data.repos)).toBe(true);
|
|
expect(data.repos.length).toBeLessThanOrEqual(5);
|
|
});
|
|
|
|
test("GET /api/platform/repos 支持搜索", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.get("/api/platform/repos?search=test");
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(Array.isArray(data.repos)).toBe(true);
|
|
});
|
|
|
|
test("GET /api/platform/repos 支持按项目筛选", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const reposRes = await page.request.get("/api/platform/repos?pageSize=1");
|
|
const reposData = await reposRes.json();
|
|
const projectId = reposData.repos?.[0]?.projectId;
|
|
if (!projectId) { test.skip(); }
|
|
|
|
const res = await page.request.get(`/api/platform/repos?projectId=${projectId}`);
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(Array.isArray(data.repos)).toBe(true);
|
|
});
|
|
|
|
test("GET /api/admin/repos/[id] 返回仓库详情含分支和提交", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const reposRes = await page.request.get("/api/platform/repos?pageSize=1");
|
|
const reposData = await reposRes.json();
|
|
const repoId = reposData.repos?.[0]?.id;
|
|
if (!repoId) { test.skip(); }
|
|
|
|
const res = await page.request.get(`/api/admin/repos/${repoId}`);
|
|
expect(res.status()).toBe(200);
|
|
const data = await res.json();
|
|
expect(data).toHaveProperty("repo");
|
|
expect(data).toHaveProperty("branches");
|
|
expect(data).toHaveProperty("commits");
|
|
expect(Array.isArray(data.branches)).toBe(true);
|
|
expect(Array.isArray(data.commits)).toBe(true);
|
|
});
|
|
|
|
test("GET /api/admin/repos/[id] 不存在的仓库返回404", async ({ page }) => {
|
|
if (!await checkBackendAvailable()) { test.skip(); }
|
|
|
|
if (!await uiLogin(page)) { test.skip(); }
|
|
|
|
const res = await page.request.get("/api/admin/repos/nonexistent-repo-id");
|
|
expect(res.status()).toBe(404);
|
|
});
|
|
});
|