const Fastify = require("fastify"); const { AppError } = require("../../../src/utils/AppError"); const leaveRoutes = require("../../../src/routes/leave"); function buildLeaveRow({ id = "11111111-1111-4111-8111-111111111111", ownerId = "employee-1", departmentId = "11111111-1111-4111-8111-111111111112", } = {}) { const now = new Date("2026-04-28T09:00:00.000Z"); return { id, owner_id: ownerId, department_id: departmentId, leave_type_id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", start_date: new Date("2026-04-29T00:00:00.000Z"), end_date: new Date("2026-04-29T00:00:00.000Z"), start_session: "am", end_session: "pm", requested_days: 1, reason: "团队请假测试", attachment_original_name: null, attachment_mime_type: null, status: "reviewing", created_at: now, updated_at: now, leave_type: { id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", code: "annual", name: "年假", requires_attachment: false, attachment_mime_types: [], }, owner: { id: ownerId, employee_no: "E0101", name: "Employee", }, }; } function buildPrisma(items = [buildLeaveRow()]) { return { leaveRequest: { findMany: vi.fn().mockResolvedValue(items), count: vi.fn().mockResolvedValue(items.length), }, }; } async function buildApp({ employee, prisma }) { const app = Fastify(); app.decorate("prisma", prisma); app.decorate("config", { cutoverConsoleEnabled: false, leaveHolidayCalendarEnabled: false, }); app.decorate("authenticate", async (request) => { if (!request.headers.authorization) { throw new AppError({ code: "AUTH_001", message: "未登录", statusCode: 401, }); } request.employee = employee; }); app.decorate("hasPermission", (actor, permissionCode) => { return (actor?.permissionCodes ?? []).includes("*") || (actor?.permissionCodes ?? []).includes(permissionCode); }); app.decorate("requireEntityPermission", (_entity, action) => async (request) => { await app.authenticate(request); const permissionCode = action === "view_own" ? "leave.view_own" : action; if (!app.hasPermission(request.employee, permissionCode)) { throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403, details: { permission: permissionCode }, }); } }); app.setErrorHandler((error, request, reply) => { if (error instanceof AppError) { return reply.status(error.statusCode).send({ requestId: request.id, error: { code: error.code, message: error.message, details: error.details ?? {}, }, }); } return reply.status(500).send({ requestId: request.id, error: { code: "SYS_001", message: "服务器内部错误", details: {} }, }); }); await app.register(leaveRoutes, { prefix: "/api" }); return app; } describe("leave list scope", () => { it("allows manager to read team calendar window for own department", async () => { const departmentId = "11111111-1111-4111-8111-111111111112"; const prisma = buildPrisma([buildLeaveRow({ departmentId })]); const app = await buildApp({ employee: { id: "manager-1", department_id: departmentId, permissionCodes: ["leave.view_own", "leave.view_all", "leave.approve"], }, prisma, }); const response = await app.inject({ method: "GET", url: `/api/leave-requests?scope=all&dateFrom=2026-04-28&dateTo=2026-05-05&departmentId=${departmentId}`, headers: { authorization: "Bearer test" }, }); expect(response.statusCode).toBe(200); expect(response.json().data.scope).toBe("all"); expect(prisma.leaveRequest.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ department_id: departmentId, }), }), ); await app.close(); }); it("rejects employee when requesting all-scope leave records", async () => { const departmentId = "11111111-1111-4111-8111-111111111112"; const app = await buildApp({ employee: { id: "employee-1", department_id: departmentId, permissionCodes: ["leave.view_own"], }, prisma: buildPrisma(), }); const response = await app.inject({ method: "GET", url: "/api/leave-requests?scope=all&dateFrom=2026-04-28&dateTo=2026-05-05", headers: { authorization: "Bearer test" }, }); expect(response.statusCode).toBe(403); expect(response.json().error.code).toBe("PERM_001"); await app.close(); }); it("rejects manager when querying another department without leave.manage", async () => { const departmentId = "11111111-1111-4111-8111-111111111112"; const otherDepartmentId = "22222222-2222-4222-8222-222222222222"; const app = await buildApp({ employee: { id: "manager-1", department_id: departmentId, permissionCodes: ["leave.view_own", "leave.view_all", "leave.approve"], }, prisma: buildPrisma(), }); const response = await app.inject({ method: "GET", url: `/api/leave-requests?scope=all&dateFrom=2026-04-28&dateTo=2026-05-05&departmentId=${otherDepartmentId}`, headers: { authorization: "Bearer test" }, }); expect(response.statusCode).toBe(403); expect(response.json().error.code).toBe("PERM_001"); expect(response.json().error.details).toEqual(expect.objectContaining({ departmentId: "forbidden" })); await app.close(); }); });