104 lines
3.7 KiB
JavaScript
104 lines
3.7 KiB
JavaScript
import { describe, expect, it, vi } from "vitest";
|
|
import { createRequire } from "module";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const { applyRedisHelpers } = require("./redisHelpers");
|
|
|
|
/**
|
|
* Creates an in-memory Redis-like test double for feature flag cache helper tests.
|
|
*/
|
|
const createRedis = () => {
|
|
const values = new Map();
|
|
const expirations = new Map();
|
|
|
|
return {
|
|
del: vi.fn(async (...keys) => {
|
|
let deleted = 0;
|
|
for (const key of keys) {
|
|
if (values.delete(key)) deleted += 1;
|
|
}
|
|
return deleted;
|
|
}),
|
|
expire: vi.fn(async (key, ttl) => {
|
|
expirations.set(key, ttl);
|
|
return 1;
|
|
}),
|
|
get: vi.fn(async (key) => values.get(key) ?? null),
|
|
incr: vi.fn(async (key) => {
|
|
const nextValue = Number(values.get(key) || 0) + 1;
|
|
values.set(key, String(nextValue));
|
|
return nextValue;
|
|
}),
|
|
set: vi.fn(async (key, value) => {
|
|
values.set(key, String(value));
|
|
return "OK";
|
|
}),
|
|
setnx: vi.fn(async (key, value) => {
|
|
if (values.has(key)) return 0;
|
|
values.set(key, String(value));
|
|
return 1;
|
|
}),
|
|
values,
|
|
expirations
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Applies Redis helpers to the in-memory test double and returns the mounted API.
|
|
*/
|
|
const createHelpers = () => {
|
|
const pubClient = createRedis();
|
|
const app = { use: vi.fn() };
|
|
const logger = { log: vi.fn() };
|
|
const helpers = applyRedisHelpers({ pubClient, app, logger });
|
|
|
|
return { app, helpers, logger, pubClient };
|
|
};
|
|
|
|
describe("feature flag Redis cache helpers", () => {
|
|
it("stores and reads bodyshop feature flags under the current cache version", async () => {
|
|
const { helpers, pubClient } = createHelpers();
|
|
|
|
await helpers.setBodyshopFeatureFlagsInRedis("shop-1", { flags: { Demo: { treatment: "on" } } });
|
|
|
|
expect(await helpers.getBodyshopFeatureFlagsCacheVersion()).toBe("1");
|
|
expect(await helpers.getBodyshopFeatureFlagsFromRedis("shop-1")).toEqual({
|
|
flags: {
|
|
Demo: {
|
|
treatment: "on"
|
|
}
|
|
}
|
|
});
|
|
expect(pubClient.values.has("bodyshop-feature-flags:v1:shop-1")).toBe(true);
|
|
expect(pubClient.expirations.get("bodyshop-feature-flags:v1:shop-1")).toBe(3600);
|
|
});
|
|
|
|
it("global invalidation bumps the cache version instead of deleting old versioned keys", async () => {
|
|
const { helpers, pubClient } = createHelpers();
|
|
|
|
await helpers.setBodyshopFeatureFlagsInRedis("shop-1", { flags: { Demo: { treatment: "on" } } });
|
|
const nextVersion = await helpers.invalidateAllBodyshopFeatureFlagsInRedis();
|
|
|
|
expect(nextVersion).toBe(2);
|
|
expect(pubClient.del).not.toHaveBeenCalledWith("bodyshop-feature-flags:v1:shop-1");
|
|
expect(await helpers.getBodyshopFeatureFlagsFromRedis("shop-1")).toBeNull();
|
|
expect(pubClient.values.has("bodyshop-feature-flags:v1:shop-1")).toBe(true);
|
|
});
|
|
|
|
it("bodyshop invalidation deletes only the current version cache key for that shop", async () => {
|
|
const { helpers, pubClient } = createHelpers();
|
|
|
|
await helpers.setBodyshopFeatureFlagsInRedis("shop-1", { flags: { Demo: { treatment: "on" } } });
|
|
await helpers.setBodyshopFeatureFlagsInRedis("shop-2", { flags: { Demo: { treatment: "on" } } });
|
|
await helpers.invalidateAllBodyshopFeatureFlagsInRedis();
|
|
await helpers.setBodyshopFeatureFlagsInRedis("shop-1", { flags: { Demo: { treatment: "off" } } });
|
|
await helpers.setBodyshopFeatureFlagsInRedis("shop-2", { flags: { Demo: { treatment: "on" } } });
|
|
|
|
await helpers.invalidateBodyshopFeatureFlagsInRedis("shop-1");
|
|
|
|
expect(pubClient.values.has("bodyshop-feature-flags:v1:shop-1")).toBe(true);
|
|
expect(pubClient.values.has("bodyshop-feature-flags:v2:shop-1")).toBe(false);
|
|
expect(pubClient.values.has("bodyshop-feature-flags:v2:shop-2")).toBe(true);
|
|
});
|
|
});
|