gitdataai/admin/tests/04-platform-rooms.spec.ts
ZhenYi a7e31d5649 feat(tests): add comprehensive Playwright integration tests for all API endpoints
- 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.
2026-04-20 22:37:05 +08:00

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);
});
});