文件
hyb-oa/server/tests/unit/featureFlags.evaluate.test.js
T

202 行
6.0 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 },
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 },
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 },
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 },
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 },
flagKey: FEATURE_FLAG_KEYS.erpSync,
strictDepartment: true,
});
const blockedDepartment = await evaluateFeatureFlag({
prisma,
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true },
flagKey: FEATURE_FLAG_KEYS.erpSync,
departmentId: "dept-b",
strictDepartment: true,
});
const allowedDepartment = await evaluateFeatureFlag({
prisma,
config: { erpSyncEnabled: true, invoiceVerificationEnabled: 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 },
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 },
flagKey: FEATURE_FLAG_KEYS.invoiceVerification,
strictDepartment: true,
});
expect(result.enabled).toBe(false);
expect(result.reason).toBe("department_allowlist_empty");
});
});
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(2);
expect(items.map((item) => item.flagKey).sort()).toEqual([
FEATURE_FLAG_KEYS.erpSync,
FEATURE_FLAG_KEYS.invoiceVerification,
]);
const missingItem = items.find((item) => item.flagKey === FEATURE_FLAG_KEYS.invoiceVerification);
expect(missingItem.id).toBeNull();
expect(missingItem.effectiveEnabled).toBe(false);
});
});