import type { Page, Route } from "@playwright/test"; import { test, expect } from "./helpers/fixtures"; import { MANAGER, login } from "./helpers/auth"; import { TEST_IDS } from "./helpers/testIds"; test.use({ serviceWorkers: "block" }); function envelope(data: T) { return { requestId: "mock-workflow-boundary", data }; } async function fulfillJson(route: Route, data: T) { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(envelope(data)), }); } const manager = { id: "manager-wecom-boundary", employeeNo: "M-WECOM", name: "李研发", email: "manager@hyinda.com", phone: null, roleCodes: ["manager"], profileCompleteness: { hasEmergencyContact: true, hasBankAccount: true, hasAvatar: false, hasIdNumber: false, score: 70, }, }; const pendingLeaveTask = { id: "task-wecom-leave-1", instanceId: "instance-wecom-leave-1", stepId: "manager-review", stepName: "主管审批", assigneeId: manager.id, assigneeRole: "manager", status: "pending", outcome: null, createdAt: new Date().toISOString(), completedAt: null, workflowInstance: { entityType: "leave", entityId: "leave-wecom-1", currentStatus: "reviewing", }, businessSummary: { reference: "QJ-WECOM-001", title: "企微主办请假镜像", applicantName: "Employee", amount: null, duration: "1 天", businessStatus: "reviewing", }, allowedActions: ["approve", "reject", "return"], }; function navigationItem(key: string, href: string, label: string, icon: string, placement = "main") { return { key, href, label, icon, placement, permissionCodes: [], featureFlagKey: null }; } async function mockWorkflowBoundaryApis(page: Page) { const actionCalls = { approve: 0 }; await page.route((url) => url.pathname.startsWith("/api/"), async (route) => { const request = route.request(); const url = new URL(request.url()); const path = url.pathname; if (path === "/api/auth/sso/providers") { await fulfillJson(route, []); return; } if (path === "/api/auth/wecom/config") { await fulfillJson(route, { enabled: true, entryEnabled: true, oauthEnabled: true, corpId: "wx-test-corp", agentId: "1000002", loginUrl: "/api/auth/wecom/login?returnTo=%2Fportal", bindEnabled: true, defaultReturnTo: "/portal", status: "ready", statusReasons: [], }); return; } if (path === "/api/auth/login" || path === "/api/auth/refresh") { await route.fulfill({ status: 200, contentType: "application/json", headers: { "set-cookie": "has_session=1; Path=/; SameSite=Lax" }, body: JSON.stringify(envelope({ accessToken: "mock-workflow-boundary-token", expiresIn: 900, mustChangePassword: false, })), }); return; } if (path === "/api/auth/logout") { await route.fulfill({ status: 200, contentType: "application/json", headers: { "set-cookie": "has_session=; Path=/; Max-Age=0" }, body: JSON.stringify(envelope({})), }); return; } if (path === "/api/me") { await fulfillJson(route, manager); return; } if (path === "/api/system/navigation") { await fulfillJson(route, { workspace: { id: "workspace-wecom-boundary", persisted: true, profile: "small_team_internal", orgMode: "single_company", primaryEntryMode: "password", notificationStrategy: "in_app", devicePriority: "desktop", moduleVisibility: {}, createdAt: null, updatedAt: null, updatedBy: null, }, sections: { main: [ navigationItem("portal", "/portal", "工作台", "home_app_logo"), navigationItem("workflow", "/workflow/tasks", "待我审批", "fact_check"), navigationItem("leave", "/leave-requests", "请假记录", "event_available"), ], admin: [], backup: [], }, recommendedRolePresets: [], }); return; } if (path === "/api/notifications") { await fulfillJson(route, { page: 1, pageSize: Number(url.searchParams.get("pageSize") ?? 5), total: 0, unreadOnly: url.searchParams.get("unreadOnly") === "1", unreadCount: 0, items: [], }); return; } if (path === "/api/portal/home") { await fulfillJson(route, { hero: { title: "工作台", subtitle: "企微主办边界测试" }, metrics: [], todos: [], notifications: [], }); return; } if (path === "/api/workflow/tasks/summary") { await fulfillJson(route, { scope: "mine", pendingTotal: 1, overdueTotal: 0, earliestPendingAt: pendingLeaveTask.createdAt, averagePendingAgeHours: 1, byEntityType: [{ key: "leave", pendingTotal: 1 }], byStep: [], byAssigneeRole: [], }); return; } if (path === "/api/workflow/tasks") { const status = url.searchParams.get("status") ?? "pending"; const items = status === "pending" ? [pendingLeaveTask] : []; await fulfillJson(route, { page: 1, pageSize: Number(url.searchParams.get("pageSize") ?? 20), total: items.length, items, }); return; } if (/^\/api\/workflow\/tasks\/[^/]+\/approve$/.test(path)) { actionCalls.approve += 1; await fulfillJson(route, { ok: true }); return; } await fulfillJson(route, {}); }); return actionCalls; } test("审批列表边界:企微主办请假只进入处理详情", async ({ page }) => { const actionCalls = await mockWorkflowBoundaryApis(page); await login(page, MANAGER); await page.goto("/workflow/tasks"); await expect(page.getByTestId(TEST_IDS.workflow.tasksTable)).toBeVisible(); const taskRow = page.getByTestId(TEST_IDS.workflow.taskRow).filter({ hasText: "QJ-WECOM-001" }); await expect(taskRow).toHaveCount(1); await expect(taskRow).toContainText("请假"); await expect(taskRow).toContainText("企微主办请假镜像"); await expect(taskRow.getByTestId(TEST_IDS.workflow.quickApprove)).toHaveCount(0); const detailLink = taskRow.getByTestId(TEST_IDS.workflow.detailLink); await expect(detailLink).toBeVisible(); await expect(detailLink).toHaveAttribute("href", "/workflow/tasks/task-wecom-leave-1"); expect(actionCalls.approve).toBe(0); });