const Fastify = require("fastify"); const employeesRoutes = require("../../../src/routes/employees"); const { AppError } = require("../../../src/utils/AppError"); const TARGET_EMPLOYEE_ID = "11111111-1111-4111-8111-111111111111"; const DEPARTMENT_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; function buildProfileRow(overrides = {}) { return { id: TARGET_EMPLOYEE_ID, employee_no: "E001", name: "Alice", phone: "13800000000", email: "alice@example.com", status: "active", employment_status: "active", department_id: DEPARTMENT_ID, title: "员工", direct_manager_id: null, hire_date: new Date("2026-01-10T00:00:00.000Z"), probation_end_date: new Date("2026-04-10T00:00:00.000Z"), last_working_date: null, work_location: "上海", emergency_contact_name: "联系人", emergency_contact_phone: "13900000000", notes: "none", archived_at: null, roles: [{ role_code: "employee" }], department: { id: DEPARTMENT_ID, name: "行政部" }, direct_manager: null, ...overrides, }; } async function buildApp(actor) { const app = Fastify(); let storedEmployee = buildProfileRow(); const employeeFindUnique = vi.fn(async ({ where }) => { if (where.id === TARGET_EMPLOYEE_ID) { return storedEmployee; } return null; }); const employeeUpdate = vi.fn(async ({ data }) => { storedEmployee = { ...storedEmployee, ...(data.title !== undefined ? { title: data.title } : {}), ...(data.hire_date !== undefined ? { hire_date: data.hire_date } : {}), ...(data.probation_end_date !== undefined ? { probation_end_date: data.probation_end_date } : {}), ...(data.last_working_date !== undefined ? { last_working_date: data.last_working_date } : {}), ...(data.work_location !== undefined ? { work_location: data.work_location } : {}), ...(data.emergency_contact_name !== undefined ? { emergency_contact_name: data.emergency_contact_name } : {}), ...(data.emergency_contact_phone !== undefined ? { emergency_contact_phone: data.emergency_contact_phone } : {}), ...(data.notes !== undefined ? { notes: data.notes } : {}), }; return storedEmployee; }); app.decorate("config", { employeeLifecycleEnabled: true }); app.decorate("authenticate", async (request) => { request.employee = actor; }); app.decorate("requirePermission", (permissionCode) => async (request) => { request.employee = actor; const permissionCodes = actor.permissionCodes ?? []; if (!permissionCodes.includes("*") && !permissionCodes.includes(permissionCode)) { throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403 }); } }); app.decorate("hasPermission", (employee, permissionCode) => { const permissionCodes = employee.permissionCodes ?? []; return permissionCodes.includes("*") || permissionCodes.includes(permissionCode); }); app.decorate("prisma", { featureFlag: { findUnique: vi.fn().mockResolvedValue({ id: "flag-1", flag_key: "employee_lifecycle_v1", override_mode: "force_on", rollout_type: "global", department_ids: [], note: "", version: 1, created_at: new Date("2026-03-28T00:00:00.000Z"), created_by: null, updated_at: new Date("2026-03-28T00:00:00.000Z"), updated_by: null, }), }, employee: { findUnique: employeeFindUnique, update: employeeUpdate, findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, employeeChecklistTask: { findMany: vi.fn().mockResolvedValue([]), }, employeeLifecycleEvent: { findMany: vi.fn().mockResolvedValue([]), }, assetCard: { findMany: vi.fn().mockResolvedValue([]), count: vi.fn().mockResolvedValue(0), }, workflowTask: { count: vi.fn().mockResolvedValue(0), }, auditLog: { create: vi.fn().mockResolvedValue({}), }, }); await app.register(employeesRoutes, { prefix: "/api" }); return { app, employeeUpdate }; } describe("employees profile patch", () => { it("allows system.users to patch hireDate and other extended profile fields", async () => { const { app, employeeUpdate } = await buildApp({ id: "actor-1", department_id: DEPARTMENT_ID, permissionCodes: ["system.users"], }); const response = await app.inject({ method: "PATCH", url: `/api/employees/${TARGET_EMPLOYEE_ID}/profile`, payload: { title: "经理", hireDate: "2026-02-01", probationEndDate: "2026-05-01", lastWorkingDate: null, }, }); expect(response.statusCode).toBe(200); expect(employeeUpdate).toHaveBeenCalledWith( expect.objectContaining({ where: { id: TARGET_EMPLOYEE_ID }, data: expect.objectContaining({ title: "经理", hire_date: new Date("2026-02-01T00:00:00.000Z"), probation_end_date: new Date("2026-05-01T00:00:00.000Z"), last_working_date: null, }), }), ); await app.close(); }); it("rejects report.dashboard.view users patching other employee profiles", async () => { const { app } = await buildApp({ id: "actor-2", department_id: DEPARTMENT_ID, permissionCodes: ["report.dashboard.view"], }); const response = await app.inject({ method: "PATCH", url: `/api/employees/${TARGET_EMPLOYEE_ID}/profile`, payload: { notes: "should fail" }, }); expect(response.statusCode).toBe(403); await app.close(); }); });