const Fastify = require("fastify"); const exportsRoutes = require("../../../src/routes/exports"); const { AppError } = require("../../../src/utils/AppError"); const DEPARTMENT_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; async function buildApp(actor) { const app = Fastify(); const findMany = vi.fn().mockResolvedValue([ { id: "11111111-1111-4111-8111-111111111111", employee_no: "E001", name: "Alice", phone: "13800000000", email: "alice@example.com", status: "active", title: "经理", hire_date: new Date("2026-01-10T00:00:00.000Z"), created_at: new Date("2026-03-28T10:00:00.000Z"), department: { name: "行政部" }, }, ]); const count = vi.fn().mockResolvedValue(1); const auditCreate = vi.fn().mockResolvedValue({}); 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", { employee: { findMany, count, }, auditLog: { create: auditCreate, }, }); await app.register(exportsRoutes, { prefix: "/api" }); return { app, findMany, count, auditCreate }; } describe("employee exports route", () => { it("exports filtered employees for system.users", async () => { const { app, findMany, count, auditCreate } = await buildApp({ id: "actor-1", roleCodes: ["administration"], permissionCodes: ["system.users"], }); const response = await app.inject({ method: "GET", url: `/api/exports/employees?format=csv&status=active&departmentId=${DEPARTMENT_ID}&q=alice&title=%E7%BB%8F%E7%90%86&managerId=bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb&hireDateFrom=2026-01-01&hireDateTo=2026-01-31&employmentStatus=active`, }); expect(response.statusCode).toBe(200); expect(response.headers["content-type"]).toContain("text/csv"); expect(response.headers["content-disposition"]).toContain("employees-"); expect(findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ status: "active", department_id: DEPARTMENT_ID, direct_manager_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", employment_status: "active", hire_date: expect.objectContaining({ gte: new Date("2026-01-01T00:00:00.000Z"), lte: new Date("2026-01-31T23:59:59.999Z"), }), AND: expect.arrayContaining([ expect.objectContaining({ OR: expect.arrayContaining([ expect.objectContaining({ name: expect.any(Object) }), expect.objectContaining({ employee_no: expect.any(Object) }), ]), }), expect.objectContaining({ title: expect.objectContaining({ contains: "经理" }), }), ]), }), }), ); expect(count).toHaveBeenCalledWith({ where: expect.objectContaining({ status: "active", department_id: DEPARTMENT_ID, direct_manager_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", }), }); expect(auditCreate).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ action: "EXPORT_EMPLOYEES", extra: expect.objectContaining({ title: "经理", managerId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", hireDateFrom: "2026-01-01", hireDateTo: "2026-01-31", employmentStatus: "active", }), }), }), ); await app.close(); }); it("rejects non system.users access", async () => { const { app } = await buildApp({ id: "actor-2", roleCodes: ["manager"], permissionCodes: ["report.dashboard.view"], }); const response = await app.inject({ method: "GET", url: "/api/exports/employees?format=csv", }); expect(response.statusCode).toBe(403); await app.close(); }); });