关联 Issue: P25-1, P25-2, P25-3, P25-4, P25-5, P26-1, P26-3, P24-1, P24-2, P29-1, P29-2, P29-3, P29-4
223 行
7.2 KiB
JavaScript
223 行
7.2 KiB
JavaScript
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);
|
|
});
|
|
});
|