const Fastify = require("fastify"); const { AppError } = require("../../../src/utils/AppError"); const routes = Object.fromEntries(["payroll", "budgets", "performance", "attendance", "attendanceCorrections", "leave"].map((name) => [name, require(`../../../src/routes/${name}`)])); const ID = "11111111-1111-4111-8111-111111111111"; const budgetBody = { code: "SYNTHETIC", name: "合成预算", periodStart: "2024-02-29", periodEnd: "2026-04-30", allocatedAmount: "100.00" }; const cycleBody = { name: "合成周期", cycleType: "quarterly", startDate: "2024-02-29", endDate: "2026-04-30" }; const leaveBody = { leaveTypeId: ID, startDate: "2024-02-29", endDate: "2026-04-30", startSession: "am", endSession: "pm", reason: "synthetic" }; const cases = [ ["payroll", "PUT", `/payroll/employees/${ID}/profile`, { effectiveDate: "2024-02-29", items: [{ salaryItemId: ID, monthlyAmount: "100.00" }] }, "effectiveDate"], ...["periodStart", "periodEnd"].map((field) => ["budgets", "POST", "/budgets", budgetBody, field]), ...["startDate", "endDate"].flatMap((field) => [["performance", "POST", "/performance/cycles", cycleBody, field], ["performance", "PATCH", `/performance/cycles/${ID}`, {}, field]]), ["attendance", "GET", "/attendance/team", {}, "date"], ["attendance", "POST", "/attendance/admin/backfill", { employeeId: ID, clockInAt: "2026-04-30T09:00:00+08:00" }, "workDate"], ...["startDate", "endDate"].map((field) => ["attendance", "POST", "/attendance/wecom/sync", { startDate: "2026-04-01", endDate: "2026-04-30" }, field]), ["attendanceCorrections", "POST", "/attendance-corrections", { kind: "missed_punch", missedPunchAt: "2026-04-30T09:00:00+08:00", reason: "synthetic" }, "workDate"], ...["startDate", "endDate"].flatMap((field) => [["leave", "POST", "/leave-requests", leaveBody, field], ["leave", "PATCH", `/leave-requests/${ID}`, leaveBody, field], ["leave", "GET", "/leave-requests/working-days", { startDate: "2024-02-29", endDate: "2026-04-30", startSession: "am", endSession: "pm" }, field]]), ...["dateFrom", "dateTo"].map((field) => ["leave", "GET", "/leave-requests", {}, field]), ]; async function buildApp(name, { allowed = true, budgetSuccess = false } = {}) { const app = Fastify(); const businessCalls = []; const rejectCall = (...args) => { businessCalls.push(args); throw new Error("unexpected_business_access"); }; const prisma = new Proxy({ featureFlag: { findUnique: vi.fn(async ({ where }) => ({ flag_key: where.flag_key, override_mode: where.flag_key.includes("cutover") ? "force_off" : "force_on", rollout_type: "global", department_ids: [] })) }, $transaction: vi.fn(rejectCall), }, { get(target, key) { if (!(key in target)) target[key] = new Proxy({}, { get(model, operation) { return model[operation] ??= vi.fn(rejectCall); } }); return target[key]; } }); if (budgetSuccess) { prisma.budgetPool = { create: vi.fn(async ({ data }) => ({ id: ID, ...data })) }; prisma.auditLog = { create: vi.fn().mockResolvedValue({}) }; } const authenticate = async (request) => { request.employee = { id: ID, status: "active", roleCodes: ["admin"], permissionCodes: ["*"], department_id: null }; }; const permission = async (request) => { await authenticate(request); if (!allowed) throw new AppError({ code: "PERM_001", statusCode: 403, message: "无权限" }); }; app.decorate("config", { appTimezone: "Asia/Shanghai", employeeLifecycleEnabled: true, attendanceUnifiedEnabled: true }); app.decorate("prisma", prisma); app.decorate("authenticate", permission); app.decorate("requirePermission", () => permission); app.decorate("requireEntityPermission", () => permission); app.decorate("assertEntityPermission", () => {}); app.decorate("hasPermission", () => allowed); await app.register(require("@fastify/multipart")); app.setErrorHandler((error, request, reply) => reply.code(error.statusCode ?? 500).send({ error: { code: error.code, details: error.details } })); await app.register(routes[name], { prefix: "/api" }); return { app, prisma, businessCalls }; } describe("remaining calendar consumers", () => { it.each(cases)("%s %s %s rejects calendar overflow before business access (%j / %s)", async (name, method, path, body, field) => { const { app, prisma, businessCalls } = await buildApp(name); try { for (const date of ["2026-02-29", "2026-04-31"]) { const input = { ...body, [field]: date }; const response = await app.inject({ method, url: `/api${path}${method === "GET" ? `?${new URLSearchParams(input)}` : ""}`, ...(method === "GET" ? {} : { payload: input }) }); const legacyDisabled = name === "attendance" && ["/attendance/team", "/attendance/admin/backfill"].includes(path); expect(response.statusCode).toBe(legacyDisabled ? 409 : 400); expect(response.json().error.code).toBe("VALID_001"); if (legacyDisabled) expect(response.json().error.details).toEqual({ featureFlag: "attendance_v1_disabled", reason: "env_hard_off", reasonCode: "attendance.feature_disabled" }); else expect(response.json().error.details.fieldErrors[field]).toContain("invalid_calendar_date"); expect(prisma.$transaction).not.toHaveBeenCalled(); expect(businessCalls).toEqual([]); } } finally { await app.close(); } }); it("budget stores exact leap-day/month-end dates and retains reverse-range refusal", async () => { const { app, prisma, businessCalls } = await buildApp("budgets", { budgetSuccess: true }); try { const response = await app.inject({ method: "POST", url: "/api/budgets", payload: budgetBody }); expect(response.statusCode).toBe(200); expect(response.json().data).toMatchObject({ periodStart: "2024-02-29", periodEnd: "2026-04-30", allocatedAmount: "100.00" }); expect(prisma.budgetPool.create.mock.calls[0][0].data).toMatchObject({ period_start: new Date("2024-02-29T00:00:00Z"), period_end: new Date("2026-04-30T00:00:00Z") }); expect(prisma.auditLog.create).toHaveBeenCalledTimes(1); const reversed = await app.inject({ method: "POST", url: "/api/budgets", payload: { ...budgetBody, periodStart: "2026-05-01" } }); expect(reversed.statusCode).toBe(400); expect(reversed.json().error.details).toEqual({ periodEnd: "must_be_after_periodStart", reasonCode: "budget.period_invalid" }); expect(prisma.budgetPool.create).toHaveBeenCalledTimes(1); expect(prisma.auditLog.create).toHaveBeenCalledTimes(1); expect(businessCalls).toEqual([]); } finally { await app.close(); } }); it("budget retains permission refusal without writes", async () => { const { app, businessCalls } = await buildApp("budgets", { allowed: false }); try { expect((await app.inject({ method: "POST", url: "/api/budgets", payload: budgetBody })).statusCode).toBe(403); expect(businessCalls).toEqual([]); } finally { await app.close(); } }); });