155 lines
3.8 KiB
TypeScript
155 lines
3.8 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { createRedisAvailability } from "@/external/redis/initUtils/createRedisAvailability.js";
|
|
|
|
class FakeRedis {
|
|
status = "ready";
|
|
connectCalls = 0;
|
|
disconnectCalls: Array<boolean | undefined> = [];
|
|
pingCalls = 0;
|
|
|
|
async ping() {
|
|
this.pingCalls++;
|
|
if (this.pingCalls <= 9) {
|
|
return await new Promise<string>(() => {});
|
|
}
|
|
|
|
return "PONG";
|
|
}
|
|
|
|
disconnect(reconnect?: boolean) {
|
|
this.disconnectCalls.push(reconnect);
|
|
this.status = "end";
|
|
}
|
|
|
|
async connect() {
|
|
this.connectCalls++;
|
|
this.status = "ready";
|
|
}
|
|
}
|
|
|
|
class HealthyFakeRedis extends FakeRedis {
|
|
override async ping() {
|
|
this.pingCalls++;
|
|
return "PONG";
|
|
}
|
|
}
|
|
|
|
class ConnectingFakeRedis extends HealthyFakeRedis {
|
|
status = "connecting";
|
|
readyHandler?: () => void;
|
|
errorHandler?: (error: Error) => void;
|
|
|
|
once(event: string, handler: (...args: never[]) => void) {
|
|
if (event === "ready") this.readyHandler = handler as () => void;
|
|
if (event === "error")
|
|
this.errorHandler = handler as (error: Error) => void;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
const wait = (ms: number) =>
|
|
new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
const waitUntil = async (check: () => boolean, timeoutMs: number) => {
|
|
const startedAt = Date.now();
|
|
while (Date.now() - startedAt < timeoutMs) {
|
|
if (check()) return;
|
|
await wait(50);
|
|
}
|
|
|
|
throw new Error(`Condition not met within ${timeoutMs}ms`);
|
|
};
|
|
|
|
describe("createRedisAvailability", () => {
|
|
test("does not start monitoring when Redis is not configured", () => {
|
|
const originalSetInterval = globalThis.setInterval;
|
|
let intervalCalls = 0;
|
|
globalThis.setInterval = (((handler: TimerHandler) => {
|
|
intervalCalls++;
|
|
return 0 as unknown as ReturnType<typeof setInterval>;
|
|
}) as unknown) as typeof setInterval;
|
|
|
|
try {
|
|
const redis = new FakeRedis();
|
|
const availability = createRedisAvailability({
|
|
redis: redis as never,
|
|
hasConfig: false,
|
|
logPrefix: "RedisV2",
|
|
logType: "redis_v2_availability_state_set",
|
|
});
|
|
|
|
availability.startMonitor();
|
|
|
|
expect(intervalCalls).toBe(0);
|
|
expect(availability.shouldUseRedis()).toBe(false);
|
|
} finally {
|
|
globalThis.setInterval = originalSetInterval;
|
|
}
|
|
});
|
|
|
|
test("starts degraded before the first probe runs", () => {
|
|
const redis = new FakeRedis();
|
|
const availability = createRedisAvailability({
|
|
redis: redis as never,
|
|
hasConfig: true,
|
|
logPrefix: "RedisV2",
|
|
logType: "redis_v2_availability_state_set",
|
|
});
|
|
|
|
expect(availability.shouldUseRedis()).toBe(false);
|
|
});
|
|
|
|
test("primes healthy after a successful initial probe", async () => {
|
|
const redis = new HealthyFakeRedis();
|
|
const availability = createRedisAvailability({
|
|
redis: redis as never,
|
|
hasConfig: true,
|
|
logPrefix: "RedisV2",
|
|
logType: "redis_v2_availability_state_set",
|
|
});
|
|
|
|
await availability.prime();
|
|
|
|
expect(availability.shouldUseRedis()).toBe(true);
|
|
});
|
|
|
|
test("waits for a connecting client before priming availability", async () => {
|
|
const redis = new ConnectingFakeRedis();
|
|
const availability = createRedisAvailability({
|
|
redis: redis as never,
|
|
hasConfig: true,
|
|
logPrefix: "RedisV2",
|
|
logType: "redis_v2_availability_state_set",
|
|
});
|
|
|
|
setTimeout(() => {
|
|
redis.status = "ready";
|
|
redis.readyHandler?.();
|
|
}, 0);
|
|
|
|
await availability.prime();
|
|
|
|
expect(availability.shouldUseRedis()).toBe(true);
|
|
});
|
|
|
|
test(
|
|
"reconnects after repeated probe failures while the client still reports ready",
|
|
async () => {
|
|
const redis = new FakeRedis();
|
|
const availability = createRedisAvailability({
|
|
redis: redis as never,
|
|
hasConfig: true,
|
|
logPrefix: "RedisV2",
|
|
logType: "redis_v2_availability_state_set",
|
|
});
|
|
|
|
availability.startMonitor();
|
|
await waitUntil(() => redis.connectCalls > 0, 22_000);
|
|
availability.stopMonitor();
|
|
|
|
expect(redis.disconnectCalls).toContain(false);
|
|
},
|
|
30_000,
|
|
);
|
|
});
|