import { expect, test } from "@playwright/test"; import { createAuthenticatedApiClient, EMPLOYEE, FINANCE, FINANCE_MGR, login } from "./helpers/auth"; // Original travel chains remain byte-for-byte in their specs, owned by G19's // frozen obligations. These assertions exercise the current retained boundary. test("冻结差旅:员工入口与共享付款边界", async ({ page }) => { const employee = await createAuthenticatedApiClient(EMPLOYEE); const finance = await createAuthenticatedApiClient(FINANCE_MGR); try { expect(employee.me.roleCodes).toContain("employee"); const denied = await employee.api.post("/api/travel-requests", { data: {} }); expect(denied.status()).toBe(403); await login(page, EMPLOYEE); await page.goto("/portal"); await expect(page.getByTestId("portal-work-summary")).toBeVisible(); await expect(page.getByRole("link", { name: "差旅申请", exact: true })).toHaveCount(0); const readiness = await finance.api.post("/api/payment-batches/readiness", { data: { entityType: "travel_order", entityIds: ["ffffffff-ffff-4fff-8fff-ffffffffffff"], defaultPaymentChannel: "bank" }, }); expect(readiness.status()).toBe(200); const payload = await readiness.json(); expect(payload.data.ready).toBe(false); expect(payload.data.blockers).toContainEqual(expect.objectContaining({ reasonCode: "payment_batch.travel_payment_frozen" })); } finally { await employee.dispose(); await finance.dispose(); } }); test("冻结差旅:财务对账权限与入站导入权限边界", async ({ page }) => { const finance = await createAuthenticatedApiClient(FINANCE); try { expect(finance.me.roleCodes).toContain("finance"); for (const endpoint of ["/api/travel/reconciliation/batches", "/api/travel/orders/import", "/api/travel/transactions/import", "/api/travel/authorizations/import"]) { const denied = await finance.api.post(endpoint, { data: {} }); expect(denied.status(), endpoint).toBe(403); } await login(page, FINANCE); await page.goto("/portal"); await expect(page.getByTestId("portal-work-summary")).toBeVisible(); await expect(page.getByRole("link", { name: "差旅申请", exact: true })).toHaveCount(0); } finally { await finance.dispose(); } });