import { beforeEach, describe, expect, it, vi } from "vitest"; import { getRlsExecutionContext, runWithRlsContext } from "@hivegrid/db/rls-context.server"; const mocks = vi.hoisted(() => ({ featureFlagFindMany: vi.fn(), featureFlagUpdate: vi.fn(), featureFlagDelete: vi.fn(), featureFlagDeleteMany: vi.fn(), tenantFindMany: vi.fn(), analyticsEventGroupBy: vi.fn(), clearCacheKeyPrefix: vi.fn(), })); vi.mock("~/utils/db.server", () => ({ db: { featureFlag: { findMany: mocks.featureFlagFindMany, update: mocks.featureFlagUpdate, delete: mocks.featureFlagDelete, deleteMany: mocks.featureFlagDeleteMany, }, tenant: { findMany: mocks.tenantFindMany }, analyticsEvent: { groupBy: mocks.analyticsEventGroupBy }, }, })); vi.mock("~/utils/cache.server", () => ({ clearCacheKeyPrefix: mocks.clearCacheKeyPrefix })); import { deleteAllFeatureFlags, deleteFeatureFlag, getAllFeatureFlags, getFeatureFlagEvaluationNames, getFeatureFlagsWithEventCounts, getLegacyFeatureFlagsWithEventCounts, updateFeatureFlag, } from "./featureFlags.db.server"; describe("feature flag list projections", () => { beforeEach(() => { vi.clearAllMocks(); mocks.featureFlagFindMany.mockResolvedValue([]); mocks.featureFlagUpdate.mockResolvedValue({ id: "flag_1" }); mocks.featureFlagDelete.mockResolvedValue({ id: "flag_1" }); mocks.featureFlagDeleteMany.mockResolvedValue({ count: 2 }); mocks.tenantFindMany.mockResolvedValue([]); mocks.analyticsEventGroupBy.mockResolvedValue([]); }); it("aggregates event counts across tenant scopes and isolated platform analytics", async () => { mocks.featureFlagFindMany.mockResolvedValue([ { id: "flag_1", filters: [] }, { id: "flag_2", filters: [] }, ]); mocks.tenantFindMany.mockImplementation(async () => { expect(getRlsExecutionContext()).toEqual({ userId: "operator_1" }); return [{ id: "tenant_1" }, { id: "tenant_2" }]; }); mocks.analyticsEventGroupBy.mockImplementation(async () => { const context = getRlsExecutionContext(); if (context?.tenantId === "tenant_1") return [{ featureFlagId: "flag_1", _count: 2 }]; if (context?.tenantId === "tenant_2") { return [ { featureFlagId: "flag_1", _count: 3 }, { featureFlagId: "flag_2", _count: 1 }, ]; } if (context?.platformAnalytics) { expect(context).toEqual({ platformAnalytics: true }); return [ { featureFlagId: "flag_1", _count: 4 }, { featureFlagId: "flag_2", _count: 5 }, ]; } throw new Error(`Unexpected RLS context: ${JSON.stringify(context)}`); }); const result = await runWithRlsContext({ userId: "operator_1" }, () => getAllFeatureFlags()); expect(result).toEqual([ expect.objectContaining({ id: "flag_1", _count: { events: 9 } }), expect.objectContaining({ id: "flag_2", _count: { events: 6 } }), ]); expect(mocks.featureFlagFindMany).toHaveBeenCalledWith({ where: { AND: [{ archivedAt: null }] }, include: { filters: true }, orderBy: { createdAt: "desc" }, }); expect(mocks.tenantFindMany).toHaveBeenCalledWith({ select: { id: true }, orderBy: { id: "asc" } }); expect(mocks.analyticsEventGroupBy).toHaveBeenCalledTimes(3); expect(mocks.analyticsEventGroupBy).toHaveBeenCalledWith({ by: ["featureFlagId"], where: { featureFlagId: { in: ["flag_1", "flag_2"] } }, _count: true, }); expect(getRlsExecutionContext()).toBeNull(); }); it("applies the enabled filter before aggregating matching flag events", async () => { mocks.featureFlagFindMany.mockResolvedValue([{ id: "flag_1", filters: [] }]); await runWithRlsContext({ userId: "operator_1" }, () => getFeatureFlagsWithEventCounts({ enabled: true })); expect(mocks.featureFlagFindMany).toHaveBeenCalledWith({ where: { AND: [{ archivedAt: null }, { enabled: true }] }, include: { filters: true }, orderBy: { createdAt: "desc" }, }); expect(mocks.analyticsEventGroupBy).toHaveBeenCalledOnce(); expect(getRlsExecutionContext()).toBeNull(); }); it("lists request-time evaluation names without cross-tenant analytics aggregation", async () => { mocks.featureFlagFindMany.mockResolvedValue([ { name: "flag-one", key: null }, { name: "flag-two", key: "legacy-two" }, { name: "flag-one", key: null }, ]); const names = await runWithRlsContext({ userId: "user_1", tenantId: "tenant_1" }, async () => { expect(getRlsExecutionContext()).toEqual({ userId: "user_1", tenantId: "tenant_1" }); return getFeatureFlagEvaluationNames(); }); expect(names).toEqual(["flag-one", "flag-two"]); expect(mocks.featureFlagFindMany).toHaveBeenCalledWith({ where: { archivedAt: null }, select: { name: true, key: true }, orderBy: { createdAt: "desc" }, }); expect(mocks.tenantFindMany).not.toHaveBeenCalled(); expect(mocks.analyticsEventGroupBy).not.toHaveBeenCalled(); expect(getRlsExecutionContext()).toBeNull(); }); it("deletes flags without deleting their analytics history", async () => { await expect(deleteAllFeatureFlags()).resolves.toEqual({ count: 2 }); expect(mocks.featureFlagDeleteMany).toHaveBeenCalledOnce(); expect(mocks.featureFlagDeleteMany).toHaveBeenCalledWith(); expect(mocks.clearCacheKeyPrefix).toHaveBeenCalledWith("featureFlags:enabled"); }); it("hides archived flags from the legacy editor", async () => { await getLegacyFeatureFlagsWithEventCounts({ enabled: undefined }); expect(mocks.featureFlagFindMany).toHaveBeenCalledWith({ where: { AND: [{ archivedAt: null }, { enabled: undefined }], }, include: { filters: true }, orderBy: { createdAt: "desc" }, }); }); it("updates active generic rollout flags", async () => { await expect(updateFeatureFlag("flag_1", { enabled: false })).resolves.toEqual({ id: "flag_1" }); expect(mocks.featureFlagUpdate).toHaveBeenCalledWith( expect.objectContaining({ where: { id: "flag_1" }, data: expect.objectContaining({ enabled: false }) }) ); }); it("deletes active generic rollout flags", async () => { await expect(deleteFeatureFlag("flag_1")).resolves.toEqual({ id: "flag_1" }); expect(mocks.featureFlagDelete).toHaveBeenCalledWith({ where: { id: "flag_1" } }); }); });