57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { RedisService } from "../redis/redis.service.js";
|
|
import { ChatRateLimiter } from "./chat-rate-limiter.js";
|
|
|
|
type FakeRedisClient = {
|
|
isReady: boolean;
|
|
isOpen: boolean;
|
|
on: ReturnType<typeof vi.fn>;
|
|
connect: ReturnType<typeof vi.fn>;
|
|
eval: ReturnType<typeof vi.fn>;
|
|
destroy: ReturnType<typeof vi.fn>;
|
|
quit: ReturnType<typeof vi.fn>;
|
|
};
|
|
|
|
function fakeClient(evalResult: Promise<unknown>): FakeRedisClient {
|
|
return {
|
|
isReady: true,
|
|
isOpen: true,
|
|
on: vi.fn(),
|
|
connect: vi.fn(() => Promise.resolve()),
|
|
eval: vi.fn(() => evalResult),
|
|
destroy: vi.fn(),
|
|
quit: vi.fn(() => Promise.resolve()),
|
|
};
|
|
}
|
|
|
|
describe("ChatRateLimiter Redis command deadline", () => {
|
|
const previousTimeout = process.env.REDIS_OPERATION_TIMEOUT_MS;
|
|
|
|
afterEach(() => {
|
|
if (previousTimeout === undefined)
|
|
delete process.env.REDIS_OPERATION_TIMEOUT_MS;
|
|
else process.env.REDIS_OPERATION_TIMEOUT_MS = previousTimeout;
|
|
});
|
|
|
|
it("destroys a client whose eval never settles and recovers with a new client", async () => {
|
|
process.env.REDIS_OPERATION_TIMEOUT_MS = "20";
|
|
const stuck = fakeClient(new Promise(() => undefined));
|
|
const healthy = fakeClient(Promise.resolve(1));
|
|
const factory = vi
|
|
.fn()
|
|
.mockReturnValueOnce(stuck)
|
|
.mockReturnValueOnce(healthy);
|
|
const redis = new RedisService(factory as never);
|
|
const limiter = new ChatRateLimiter(redis);
|
|
|
|
await expect(limiter.consume("account", "session")).rejects.toThrow(
|
|
"Auth Redis operation timed out",
|
|
);
|
|
expect(stuck.destroy).toHaveBeenCalledOnce();
|
|
|
|
await expect(limiter.consume("account", "session")).resolves.toBe(true);
|
|
expect(healthy.eval).toHaveBeenCalledOnce();
|
|
expect(factory).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|