完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)
这个提交包含在:
@@ -9,12 +9,18 @@ function envelope(data: unknown) {
|
||||
|
||||
function mockCommonAuth(page: import("@playwright/test").Page, options: {
|
||||
mustChangePassword?: boolean;
|
||||
meId?: string;
|
||||
meName?: string;
|
||||
meEmployeeNo?: string;
|
||||
permissions?: string[];
|
||||
roleCodes?: string[];
|
||||
}) {
|
||||
const mustChangePassword = options.mustChangePassword === true;
|
||||
const meId = options.meId ?? "employee-admin-1";
|
||||
const meName = options.meName ?? "Admin";
|
||||
const meEmployeeNo = options.meEmployeeNo ?? "HYD000001";
|
||||
const permissions = options.permissions ?? ["system.users", "system.accounts.manage", "system.roles.manage"];
|
||||
const roleCodes = options.roleCodes ?? ["admin"];
|
||||
|
||||
return Promise.all([
|
||||
page.route("**/api/auth/sso/providers", async (route) => {
|
||||
@@ -64,7 +70,7 @@ function mockCommonAuth(page: import("@playwright/test").Page, options: {
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(
|
||||
envelope({
|
||||
id: "employee-admin-1",
|
||||
id: meId,
|
||||
employeeNo: meEmployeeNo,
|
||||
name: meName,
|
||||
phone: "13800000000",
|
||||
@@ -73,8 +79,8 @@ function mockCommonAuth(page: import("@playwright/test").Page, options: {
|
||||
directManagerId: null,
|
||||
status: "active",
|
||||
notificationPushEnabled: true,
|
||||
roleCodes: ["admin"],
|
||||
permissions: ["system.users"],
|
||||
roleCodes,
|
||||
permissions,
|
||||
profileCompleteness: {
|
||||
hasEmergencyContact: true,
|
||||
hasBankAccount: true,
|
||||
@@ -135,6 +141,73 @@ function mockCommonAuth(page: import("@playwright/test").Page, options: {
|
||||
]);
|
||||
}
|
||||
|
||||
type MockUser = {
|
||||
id: string;
|
||||
employeeNo: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
status: "active" | "archived";
|
||||
departmentId: string;
|
||||
title: string;
|
||||
roleCodes: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
function buildMockUser(id: string, name: string, roleCodes: string[]): MockUser {
|
||||
return {
|
||||
id,
|
||||
employeeNo: `MOCK-${id}`,
|
||||
name,
|
||||
phone: "00000000000",
|
||||
email: `${id}@example.test`,
|
||||
status: "active",
|
||||
departmentId: "dept-1",
|
||||
title: "员工",
|
||||
roleCodes,
|
||||
createdAt: "2026-07-18T00:00:00.000Z",
|
||||
updatedAt: "2026-07-18T00:00:00.000Z",
|
||||
};
|
||||
}
|
||||
|
||||
async function mockUserManagementData(
|
||||
page: import("@playwright/test").Page,
|
||||
items: MockUser[],
|
||||
) {
|
||||
await Promise.all([
|
||||
page.route((url) => url.pathname === "/api/users", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(envelope({ page: 1, pageSize: 20, total: items.length, items })),
|
||||
});
|
||||
}),
|
||||
page.route((url) => url.pathname === "/api/departments", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(envelope({ includeArchived: false, items: [], tree: [] })),
|
||||
});
|
||||
}),
|
||||
page.route((url) => url.pathname === "/api/employees", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(envelope({ page: 1, pageSize: 200, total: 0, items: [] })),
|
||||
});
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
async function loginToUserManagement(page: import("@playwright/test").Page) {
|
||||
await page.goto("/login?redirect=%2Fsystem%2Fusers");
|
||||
await page.getByTestId("login-identifier").fill("[email protected]");
|
||||
await page.getByTestId("login-password").fill("SyntheticPassword1234");
|
||||
await page.getByTestId("login-submit").click();
|
||||
await expect(page).toHaveURL(/\/system\/users/);
|
||||
}
|
||||
|
||||
test("E2E 用户开通:新建用户时由系统生成工号和一次性口令", async ({ page }) => {
|
||||
let createPayload: Record<string, unknown> | null = null;
|
||||
|
||||
@@ -234,6 +307,60 @@ test("E2E 用户开通:新建用户时由系统生成工号和一次性口令"
|
||||
await expect(page.getByTestId("users-reset-result")).toContainText("ResetA1abc123def456");
|
||||
});
|
||||
|
||||
test("E2E 账号保护:OA 系统管理员只能操作普通账号", async ({ page }) => {
|
||||
const currentId = "system-admin-current";
|
||||
const ordinary = buildMockUser("ordinary-user", "普通员工", ["employee"]);
|
||||
const protectedAdmin = buildMockUser("protected-admin", "应急账号甲", ["employee", "admin"]);
|
||||
const protectedSystemAdmin = buildMockUser("protected-system-admin", "运维账号乙", ["employee", "system_admin"]);
|
||||
const currentSystemAdmin = buildMockUser(currentId, "当前运维账号", ["employee", "system_admin"]);
|
||||
|
||||
await mockCommonAuth(page, {
|
||||
meId: currentId,
|
||||
meName: "当前运维账号",
|
||||
roleCodes: ["system_admin"],
|
||||
permissions: ["system.accounts.manage", "system.roles.manage"],
|
||||
});
|
||||
await mockUserManagementData(page, [ordinary, protectedAdmin, protectedSystemAdmin, currentSystemAdmin]);
|
||||
await loginToUserManagement(page);
|
||||
|
||||
const ordinaryRow = page.getByTestId("users-row").filter({ hasText: ordinary.name });
|
||||
await expect(ordinaryRow.getByTestId("users-toggle")).toHaveCount(1);
|
||||
await expect(ordinaryRow.getByTestId("users-reset-password")).toHaveCount(1);
|
||||
await expect(ordinaryRow.getByTestId("users-edit-roles")).toHaveCount(1);
|
||||
|
||||
for (const user of [protectedAdmin, protectedSystemAdmin, currentSystemAdmin]) {
|
||||
const row = page.getByTestId("users-row").filter({ hasText: user.name });
|
||||
await expect(row.getByTestId("users-toggle")).toHaveCount(0);
|
||||
await expect(row.getByTestId("users-reset-password")).toHaveCount(0);
|
||||
await expect(row.getByTestId("users-edit-roles")).toHaveCount(0);
|
||||
await expect(row.getByTestId("users-protected-account")).toHaveText("受保护账号");
|
||||
}
|
||||
});
|
||||
|
||||
test("E2E 账号保护:应急超级管理员可操作其他受保护账号但不能停用或改自己的角色", async ({ page }) => {
|
||||
const currentId = "employee-admin-1";
|
||||
const otherAdmin = buildMockUser("other-admin", "其他应急账号", ["employee", "admin"]);
|
||||
const otherSystemAdmin = buildMockUser("other-system-admin", "其他运维账号", ["employee", "system_admin"]);
|
||||
const currentAdmin = buildMockUser(currentId, "当前应急账号", ["employee", "admin"]);
|
||||
|
||||
await mockCommonAuth(page, { meId: currentId, meName: currentAdmin.name });
|
||||
await mockUserManagementData(page, [otherAdmin, otherSystemAdmin, currentAdmin]);
|
||||
await loginToUserManagement(page);
|
||||
|
||||
for (const user of [otherAdmin, otherSystemAdmin]) {
|
||||
const row = page.getByTestId("users-row").filter({ hasText: user.name });
|
||||
await expect(row.getByTestId("users-toggle")).toHaveCount(1);
|
||||
await expect(row.getByTestId("users-reset-password")).toHaveCount(1);
|
||||
await expect(row.getByTestId("users-edit-roles")).toHaveCount(1);
|
||||
await expect(row.getByTestId("users-protected-account")).toHaveCount(0);
|
||||
}
|
||||
|
||||
const currentRow = page.getByTestId("users-row").filter({ hasText: currentAdmin.name });
|
||||
await expect(currentRow.getByTestId("users-toggle")).toHaveCount(0);
|
||||
await expect(currentRow.getByTestId("users-edit-roles")).toHaveCount(0);
|
||||
await expect(currentRow.getByTestId("users-reset-password")).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("E2E 首次登录:一次性口令登录后强制改密", async ({ page }) => {
|
||||
let changePasswordPayload: Record<string, unknown> | null = null;
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户