const { FEATURE_FLAG_KEYS, evaluateFeatureFlag, listFeatureFlags, } = require("../../src/services/featureFlags"); function buildRecord({ flagKey, overrideMode = "force_on", rolloutType = "global", departmentIds = [], }) { const now = new Date("2026-02-12T00:00:00.000Z"); return { id: `flag-${flagKey}`, flag_key: flagKey, override_mode: overrideMode, rollout_type: rolloutType, department_ids: departmentIds, note: "unit-test", version: 1, created_at: now, created_by: null, updated_at: now, updated_by: null, }; } function buildPrisma(recordMap = {}) { return { featureFlag: { findUnique: async ({ where }) => recordMap[where.flag_key] ?? null, findMany: async () => Object.values(recordMap), }, }; } describe("evaluateFeatureFlag", () => { it("returns env_hard_off when env gate is closed", async () => { const result = await evaluateFeatureFlag({ prisma: buildPrisma(), config: { erpSyncEnabled: false, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.erpSync, }); expect(result.enabled).toBe(false); expect(result.reason).toBe("env_hard_off"); }); it("returns missing_flag_record when record does not exist", async () => { const result = await evaluateFeatureFlag({ prisma: buildPrisma(), config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.erpSync, }); expect(result.enabled).toBe(false); expect(result.reason).toBe("missing_flag_record"); }); it("returns flag_force_off when override mode is force_off", async () => { const prisma = buildPrisma({ [FEATURE_FLAG_KEYS.erpSync]: buildRecord({ flagKey: FEATURE_FLAG_KEYS.erpSync, overrideMode: "force_off", }), }); const result = await evaluateFeatureFlag({ prisma, config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.erpSync, }); expect(result.enabled).toBe(false); expect(result.reason).toBe("flag_force_off"); }); it("returns global enabled when inherit + global", async () => { const prisma = buildPrisma({ [FEATURE_FLAG_KEYS.erpSync]: buildRecord({ flagKey: FEATURE_FLAG_KEYS.erpSync, overrideMode: "inherit", rolloutType: "global", }), }); const result = await evaluateFeatureFlag({ prisma, config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.erpSync, }); expect(result.enabled).toBe(true); expect(result.reason).toBe("env_inherit_global_on"); }); it("returns allowlist guards for department rollout", async () => { const prisma = buildPrisma({ [FEATURE_FLAG_KEYS.erpSync]: buildRecord({ flagKey: FEATURE_FLAG_KEYS.erpSync, rolloutType: "department_allowlist", departmentIds: ["dept-a"], }), }); const missingDepartment = await evaluateFeatureFlag({ prisma, config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.erpSync, strictDepartment: true, }); const blockedDepartment = await evaluateFeatureFlag({ prisma, config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.erpSync, departmentId: "dept-b", strictDepartment: true, }); const allowedDepartment = await evaluateFeatureFlag({ prisma, config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.erpSync, departmentId: "dept-a", strictDepartment: true, }); expect(missingDepartment.reason).toBe("department_missing"); expect(blockedDepartment.reason).toBe("department_not_allowed"); expect(allowedDepartment.reason).toBe("flag_force_on_department_allowed"); expect(allowedDepartment.enabled).toBe(true); }); it("returns department_deferred when strictDepartment is false", async () => { const prisma = buildPrisma({ [FEATURE_FLAG_KEYS.invoiceVerification]: buildRecord({ flagKey: FEATURE_FLAG_KEYS.invoiceVerification, rolloutType: "department_allowlist", departmentIds: ["dept-a"], }), }); const result = await evaluateFeatureFlag({ prisma, config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.invoiceVerification, strictDepartment: false, }); expect(result.enabled).toBe(true); expect(result.reason).toBe("department_deferred"); }); it("returns department_allowlist_empty when allowlist has no items", async () => { const prisma = buildPrisma({ [FEATURE_FLAG_KEYS.invoiceVerification]: buildRecord({ flagKey: FEATURE_FLAG_KEYS.invoiceVerification, rolloutType: "department_allowlist", departmentIds: [], }), }); const result = await evaluateFeatureFlag({ prisma, config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.invoiceVerification, strictDepartment: true, }); expect(result.enabled).toBe(false); expect(result.reason).toBe("department_allowlist_empty"); }); }); describe("feature flag env gates", () => { it("uses the dedicated leave holiday calendar env gate", async () => { const result = await evaluateFeatureFlag({ prisma: buildPrisma(), config: { aiAssistEnabled: true, erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: false }, flagKey: FEATURE_FLAG_KEYS.leaveHolidayCalendar, }); expect(result.enabled).toBe(false); expect(result.reason).toBe("env_hard_off"); }); it("uses the dedicated ai assist env gate", async () => { const result = await evaluateFeatureFlag({ prisma: buildPrisma(), config: { aiAssistEnabled: false, erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true }, flagKey: FEATURE_FLAG_KEYS.aiAssist, }); expect(result.enabled).toBe(false); expect(result.reason).toBe("env_hard_off"); }); }); describe("listFeatureFlags", () => { it("returns all known keys even if part of records are missing", async () => { const prisma = buildPrisma({ [FEATURE_FLAG_KEYS.erpSync]: buildRecord({ flagKey: FEATURE_FLAG_KEYS.erpSync, overrideMode: "inherit", }), }); const items = await listFeatureFlags(prisma, { erpSyncEnabled: true, invoiceVerificationEnabled: true, }); expect(items).toHaveLength(Object.keys(FEATURE_FLAG_KEYS).length); expect(items.map((item) => item.flagKey).sort()).toEqual(Object.values(FEATURE_FLAG_KEYS).sort()); const missingItem = items.find((item) => item.flagKey === FEATURE_FLAG_KEYS.aiAssist); expect(missingItem.id).toBeNull(); expect(missingItem.effectiveEnabled).toBe(false); }); });