const Fastify = require("fastify"); const { AppError } = require("../../../src/utils/AppError"); const leaveRoutes = require("../../../src/routes/leave"); function buildFlagRecord(overrides = {}) { return { id: "flag-1", flag_key: "leave_holiday_calendar", override_mode: "force_on", rollout_type: "global", department_ids: [], note: "", version: 1, created_at: new Date("2026-04-01T00:00:00.000Z"), created_by: null, updated_at: new Date("2026-04-01T00:00:00.000Z"), updated_by: null, ...overrides, }; } function buildCalendarDay(date, dayType) { return { date: new Date(`${date}T00:00:00.000Z`), day_type: dayType, }; } function buildLegalEntity(overrides = {}) { return { id: "legal-entity-1", default_holiday_calendar: { id: "calendar-1", archived_at: null, days: [ buildCalendarDay("2026-01-01", "holiday"), buildCalendarDay("2026-01-02", "holiday"), buildCalendarDay("2026-01-03", "holiday"), buildCalendarDay("2026-01-04", "adjusted_workday"), ], }, ...overrides, }; } async function buildApp(options = {}) { const app = Fastify(); const employee = options.employee ?? { id: "employee-1", department_id: "department-1", legal_entity_id: "legal-entity-1", permissionCodes: [], }; const featureFlagFindUnique = vi .fn() .mockResolvedValue(options.featureFlagRecord ?? buildFlagRecord()); const legalEntityFindUnique = vi .fn() .mockResolvedValue(options.legalEntity ?? buildLegalEntity()); app.decorate("prisma", { featureFlag: { findUnique: featureFlagFindUnique, }, legalEntity: { findUnique: legalEntityFindUnique, }, }); app.decorate("config", { cutoverConsoleEnabled: false, leaveHolidayCalendarEnabled: options.leaveHolidayCalendarEnabled === undefined ? true : options.leaveHolidayCalendarEnabled, }); app.decorate("authenticate", async (request) => { if (!request.headers.authorization) { throw new AppError({ code: "AUTH_001", message: "未登录", statusCode: 401, }); } request.employee = employee; }); app.decorate("hasPermission", () => false); app.decorate("requireEntityPermission", () => async (request) => { await app.authenticate(request); }); 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, featureFlagFindUnique, legalEntityFindUnique, }; } describe("leave working-days route", () => { it("returns preview fields and counts for ranges that include official holidays", async () => { const { app } = await buildApp(); const response = await app.inject({ method: "GET", url: "/api/leave-requests/working-days?startDate=2026-01-01&endDate=2026-01-04&startSession=am&endSession=pm", headers: { authorization: "Bearer test" }, }); expect(response.statusCode).toBe(200); expect(response.json().data).toEqual({ requestedDays: 1, naturalRequestedDays: 4, holidayDayCount: 3, weekendDayCount: 0, adjustedWorkdayCount: 1, calendarApplied: true, isNonWorkingRange: false, }); await app.close(); }); it("keeps half-day precision in preview results", async () => { const { app } = await buildApp(); const response = await app.inject({ method: "GET", url: "/api/leave-requests/working-days?startDate=2026-01-03&endDate=2026-01-04&startSession=pm&endSession=am", headers: { authorization: "Bearer test" }, }); expect(response.statusCode).toBe(200); expect(response.json().data).toEqual({ requestedDays: 0.5, naturalRequestedDays: 1, holidayDayCount: 0.5, weekendDayCount: 0, adjustedWorkdayCount: 0.5, calendarApplied: true, isNonWorkingRange: false, }); await app.close(); }); it("returns zero days instead of error for fully non-working preview ranges", async () => { const { app } = await buildApp({ legalEntity: buildLegalEntity({ default_holiday_calendar: { id: "calendar-1", archived_at: null, days: [ buildCalendarDay("2026-05-01", "holiday"), buildCalendarDay("2026-05-02", "holiday"), buildCalendarDay("2026-05-03", "holiday"), buildCalendarDay("2026-05-04", "holiday"), buildCalendarDay("2026-05-05", "holiday"), ], }, }), }); const response = await app.inject({ method: "GET", url: "/api/leave-requests/working-days?startDate=2026-05-01&endDate=2026-05-05&startSession=am&endSession=pm", headers: { authorization: "Bearer test" }, }); expect(response.statusCode).toBe(200); expect(response.json().data).toEqual({ requestedDays: 0, naturalRequestedDays: 5, holidayDayCount: 5, weekendDayCount: 0, adjustedWorkdayCount: 0, calendarApplied: true, isNonWorkingRange: true, }); await app.close(); }); it("falls back to natural days when the holiday-calendar feature is disabled", async () => { const { app, featureFlagFindUnique, legalEntityFindUnique } = await buildApp({ leaveHolidayCalendarEnabled: false, }); const response = await app.inject({ method: "GET", url: "/api/leave-requests/working-days?startDate=2026-01-01&endDate=2026-01-04&startSession=am&endSession=pm", headers: { authorization: "Bearer test" }, }); expect(response.statusCode).toBe(200); expect(response.json().data).toEqual({ requestedDays: 4, naturalRequestedDays: 4, holidayDayCount: 0, weekendDayCount: 0, adjustedWorkdayCount: 0, calendarApplied: false, isNonWorkingRange: false, }); expect(featureFlagFindUnique).not.toHaveBeenCalled(); expect(legalEntityFindUnique).not.toHaveBeenCalled(); await app.close(); }); });