Files
plp/apps/api/src/conversation/chat-rate-limiter.spec.ts
T

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);
});
});