Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/routes/admin/feature-flags/engine.validation.test.ts
T

93 lines
2.6 KiB
TypeScript

// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
findUnique: vi.fn(),
update: vi.fn(),
auditCreate: vi.fn(),
transaction: vi.fn(),
invalidateFlagCache: vi.fn(),
verifyUserHasPermission: vi.fn(),
validateCSRFToken: vi.fn(),
getUserInfo: vi.fn(),
}));
vi.mock("~/locale/i18next.server", () => ({
getTranslations: vi.fn(async () => ({
t: (key: string, options?: Record<string, string | number>) => {
if (key === "featureFlags.engine.errors.invalidCompositeOperator") {
return `${options?.path}.op must be AND, OR, or NOT.`;
}
return key;
},
})),
}));
vi.mock("~/utils/helpers/.server/PermissionsService", () => ({
verifyUserHasPermission: mocks.verifyUserHasPermission,
}));
vi.mock("~/utils/session.server", () => ({
validateCSRFToken: mocks.validateCSRFToken,
getUserInfo: mocks.getUserInfo,
}));
vi.mock("~/utils/services/.server/featureFlagEvaluator.server", () => ({
invalidateFlagCache: mocks.invalidateFlagCache,
}));
vi.mock("~/utils/db.server", () => ({
db: {
featureFlag: {
findUnique: mocks.findUnique,
update: mocks.update,
},
featureFlagAudit: {
create: mocks.auditCreate,
},
$transaction: mocks.transaction,
},
}));
function requestForRules(rules: unknown) {
const body = new URLSearchParams({
intent: "save-rules",
id: "flag_1",
rules: JSON.stringify(rules),
});
return new Request("https://admin.example.com/feature-flags/engine", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body,
});
}
describe("admin feature flag engine rule validation", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.verifyUserHasPermission.mockResolvedValue(undefined);
mocks.validateCSRFToken.mockResolvedValue(undefined);
mocks.getUserInfo.mockResolvedValue({ userId: "user_1" });
mocks.findUnique.mockResolvedValue({
id: "flag_1",
name: "checkout.beta",
enabled: true,
rules: null,
});
});
it("returns a specific error for unsupported composite operators before persisting", async () => {
const { action } = await import("./engine");
const response = (await action({
request: requestForRules({ type: "composite", op: "XOR", children: [] }),
params: {},
context: {},
})) as Response;
expect(response.status).toBe(400);
await expect(response.json()).resolves.toEqual({ error: "rule.op must be AND, OR, or NOT." });
expect(mocks.transaction).not.toHaveBeenCalled();
expect(mocks.invalidateFlagCache).not.toHaveBeenCalled();
});
});