feat: scaffold TASK-WP6-01 admin operations
Dada P0-A isolated Windows CI / validate-and-package (push) Successful in 10m45s

This commit is contained in:
suyx
2026-08-03 19:15:03 +08:00
parent f1bebab611
commit 19212cc1b6
17 changed files with 1760 additions and 17 deletions
+137
View File
@@ -0,0 +1,137 @@
import { mkdirSync } from "node:fs";
import { resolve } from "node:path";
import { expect, test, type Page } from "@playwright/test";
import { createServer, type ViteDevServer } from "vite";
import { adminOverviewFixture } from "../fixtures/wp6-01-admin-overview.js";
let vite: ViteDevServer;
let webUrl: string;
const adminSession = {
acknowledged_private_content_notice_version: null,
admin: { role: "super_admin", status: "active", user_id: "00000000-0000-4000-8000-000000000601" },
audience: "admin",
authenticated: true,
csrf_token: "csrf-admin-shell-fixture-000000000000000000000000000000000",
current_private_content_notice_version: null,
expires_at: "2026-09-02T09:30:00.000Z",
notice_acknowledged: false,
};
const navigation = [
["总览", "/admin"],
["用户与点数", "/admin/users"],
["邀请码", "/admin/invites"],
["模型", "/admin/models"],
["素材", "/admin/assets"],
["内部预览", "/admin/preview"],
["生成记录", "/admin/generations"],
["服务与存储", "/admin/services-storage"],
["审计", "/admin/audit"],
] as const;
test.beforeAll(async () => {
vite = await createServer({
configFile: resolve("apps/web/vite.config.ts"),
root: resolve("apps/web"),
server: { host: "127.0.0.1", port: 0 },
});
await vite.listen();
const address = vite.httpServer?.address();
if (!address || typeof address === "string") throw new Error("Vite did not expose a test port.");
webUrl = `http://127.0.0.1:${address.port}`;
});
test.afterAll(async () => vite.close());
async function routeActiveAdmin(page: Page) {
await page.route("**/api/v1/admin-auth/session", (route) => route.fulfill({
body: JSON.stringify(adminSession),
contentType: "application/json",
status: 200,
}));
await page.route("**/api/v1/admin/overview", (route) => route.fulfill({
body: JSON.stringify(adminOverviewFixture),
contentType: "application/json",
status: 200,
}));
}
test("TDD-WP6-ADM-001-role-and-summary renders the protected nine-entry admin shell", async ({ page }) => {
const requests: string[] = [];
page.on("request", (request) => requests.push(request.url()));
await routeActiveAdmin(page);
await page.goto(`${webUrl}/admin`);
await expect(page.getByRole("heading", { level: 2, name: "运营总览" })).toBeVisible();
const sidebar = page.getByRole("navigation", { name: "后台主导航" });
await expect(sidebar).toBeVisible();
for (const [name, href] of navigation) {
await expect(sidebar.getByRole("link", { name, exact: true })).toHaveAttribute("href", href);
}
expect(Math.round((await sidebar.boundingBox())?.width ?? 0)).toBe(216);
await expect(page.getByText("4 / 10", { exact: true })).toBeVisible();
await expect(page.getByText("待人工核对 1", { exact: true })).toBeVisible();
await expect(page.getByText("85.0%", { exact: true })).toBeVisible();
await expect(page.getByRole("link", { name: "有异常", exact: true })).toBeVisible();
await expect(page.locator("body")).not.toContainText(/forbidden|example\.invalid|api[_ -]?key|完整提示词/i);
expect(requests.some((url) => /prompt|private-content|image-content/i.test(url))).toBe(false);
const evidenceRoot = process.env.DADA_EVIDENCE_DIR_ADMIN;
if (evidenceRoot) {
const screenshotDirectory = resolve(evidenceRoot, "screenshots");
mkdirSync(screenshotDirectory, { recursive: true });
await page.screenshot({ fullPage: true, path: resolve(screenshotDirectory, "admin-overview.png") });
}
});
test("TDD-WP6-ADM-001-role-and-summary keeps ordinary and preview sessions outside admin", async ({ page }) => {
let overviewCalls = 0;
await page.route("**/api/v1/admin-auth/session", (route) => route.fulfill({
body: JSON.stringify({ error: { code: "AUTH_SESSION_INVALID", subject: "preview_user" } }),
contentType: "application/json",
status: 401,
}));
await page.route("**/api/v1/admin/overview", (route) => {
overviewCalls += 1;
return route.fulfill({ body: "null", contentType: "application/json", status: 401 });
});
await page.goto(`${webUrl}/admin`);
await expect(page).toHaveURL(`${webUrl}/admin/login`);
await expect(page.getByRole("heading", { name: "管理员邮箱验证码登录" })).toBeVisible();
expect(overviewCalls).toBe(0);
await expect(page.getByText("DADA ADMIN", { exact: true })).toHaveCount(0);
const evidenceRoot = process.env.DADA_EVIDENCE_DIR_ADMIN;
if (evidenceRoot) {
const screenshotDirectory = resolve(evidenceRoot, "screenshots");
mkdirSync(screenshotDirectory, { recursive: true });
await page.screenshot({ fullPage: true, path: resolve(screenshotDirectory, "admin-denied.png") });
}
});
test("TDD-WP6-ADM-001-role-and-summary ejects a disabled admin when the session is rechecked", async ({ page }) => {
let active = true;
await page.route("**/api/v1/admin-auth/session", (route) => route.fulfill(active ? {
body: JSON.stringify(adminSession),
contentType: "application/json",
status: 200,
} : {
body: JSON.stringify({ error: { code: "AUTH_SESSION_INVALID" } }),
contentType: "application/json",
status: 401,
}));
await page.route("**/api/v1/admin/overview", (route) => route.fulfill({
body: JSON.stringify(adminOverviewFixture),
contentType: "application/json",
status: 200,
}));
await page.goto(`${webUrl}/admin`);
await expect(page.getByRole("heading", { level: 2, name: "运营总览" })).toBeVisible();
active = false;
await page.evaluate(() => window.dispatchEvent(new Event("dada:session-invalid")));
await expect(page).toHaveURL(`${webUrl}/admin/login`);
});