50 lines
1.8 KiB
TypeScript
50 lines
1.8 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { createSharedStore, MemoryAdapter } from "@hivegrid/shared-store";
|
|
|
|
import { __resetRateLimitStoreForTests, __setRateLimitStoreForTests, rateLimitByKey } from "./rateLimitService";
|
|
|
|
const input = {
|
|
apiKey: "api-key-1",
|
|
tenantId: "tenant-1",
|
|
tenantSubscription: null,
|
|
limitByMinute: 2,
|
|
limitBySecond: 100,
|
|
};
|
|
|
|
describe("legacy API shared rate limit", () => {
|
|
beforeEach(() => {
|
|
__setRateLimitStoreForTests(createSharedStore({ namespace: "legacy-api-rate-test", adapter: new MemoryAdapter() }));
|
|
});
|
|
|
|
afterEach(() => {
|
|
__resetRateLimitStoreForTests();
|
|
});
|
|
|
|
it("enforces one counter shared by every caller instead of a process-local LRU", async () => {
|
|
await expect(rateLimitByKey(input)).resolves.toEqual({ success: true, error: null });
|
|
await expect(rateLimitByKey(input)).resolves.toEqual({ success: true, error: null });
|
|
await expect(rateLimitByKey(input)).resolves.toMatchObject({
|
|
success: false,
|
|
error: expect.stringContaining("2 request/min"),
|
|
});
|
|
});
|
|
|
|
it("atomically caps a parallel burst", async () => {
|
|
const results = await Promise.all(Array.from({ length: 20 }, () => rateLimitByKey({ ...input, apiKey: "parallel-key", limitByMinute: 5 })));
|
|
|
|
expect(results.filter((result) => result.success)).toHaveLength(5);
|
|
expect(results.filter((result) => !result.success)).toHaveLength(15);
|
|
});
|
|
|
|
it("fails closed when the shared counter is unavailable", async () => {
|
|
const adapter = new MemoryAdapter();
|
|
await adapter.close();
|
|
__setRateLimitStoreForTests(createSharedStore({ namespace: "legacy-api-rate-closed", adapter }));
|
|
|
|
await expect(rateLimitByKey(input)).resolves.toEqual({
|
|
success: false,
|
|
error: "Rate limit service temporarily unavailable",
|
|
});
|
|
});
|
|
});
|