Skip to content

Commit bd74a50

Browse files
giovaborgognoclaude
andcommitted
test(events): add Redis-backed rate limiter integration tests
Add 3 redisTest integration tests for RedisEventRateLimitChecker: - allows requests within limit - blocks requests exceeding limit - isolates keys from each other Uses @internal/testcontainers redisTest fixture with ioredis adapter. Total: 14 tests (11 existing + 3 new Redis tests), all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d53ec0b commit bd74a50

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

apps/webapp/test/engine/eventRateLimiter.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { describe, expect, test } from "vitest";
2+
import { redisTest } from "@internal/testcontainers";
3+
import { createRedisClient } from "@internal/redis";
24
import {
35
InMemoryEventRateLimitChecker,
6+
RedisEventRateLimitChecker,
47
windowToMs,
58
parseEventRateLimitConfig,
69
} from "../../app/v3/services/events/eventRateLimiter.server";
@@ -112,3 +115,104 @@ describe("InMemoryEventRateLimitChecker", () => {
112115
expect(afterReset.allowed).toBe(true);
113116
});
114117
});
118+
119+
// ─── RedisEventRateLimitChecker (needs Redis container) ───
120+
121+
function createUpstashAdapter(redis: ReturnType<typeof createRedisClient>) {
122+
return {
123+
sadd: async <TData>(key: string, ...members: TData[]): Promise<number> => {
124+
return redis.sadd(key, members as (string | number | Buffer)[]);
125+
},
126+
hset: <TValue>(
127+
key: string,
128+
obj: {
129+
[key: string]: TValue;
130+
}
131+
): Promise<number> => {
132+
return redis.hset(key, obj);
133+
},
134+
eval: <TArgs extends unknown[], TData = unknown>(
135+
...args: [script: string, keys: string[], args: TArgs]
136+
): Promise<TData> => {
137+
const script = args[0];
138+
const keys = args[1];
139+
const argsArray = args[2];
140+
return redis.eval(
141+
script,
142+
keys.length,
143+
...keys,
144+
...(argsArray as (string | Buffer | number)[])
145+
) as Promise<TData>;
146+
},
147+
};
148+
}
149+
150+
redisTest(
151+
"RedisEventRateLimitChecker allows requests within limit",
152+
{ timeout: 30_000 },
153+
async ({ redisOptions }) => {
154+
const redis = createRedisClient("test:rateLimiter", redisOptions);
155+
156+
try {
157+
const checker = new RedisEventRateLimitChecker(createUpstashAdapter(redis));
158+
const config = { limit: 3, window: "10s" };
159+
160+
const r1 = await checker.check("redis-key-1", config);
161+
expect(r1.allowed).toBe(true);
162+
163+
const r2 = await checker.check("redis-key-1", config);
164+
expect(r2.allowed).toBe(true);
165+
166+
const r3 = await checker.check("redis-key-1", config);
167+
expect(r3.allowed).toBe(true);
168+
} finally {
169+
redis.disconnect();
170+
}
171+
}
172+
);
173+
174+
redisTest(
175+
"RedisEventRateLimitChecker blocks requests exceeding limit",
176+
{ timeout: 30_000 },
177+
async ({ redisOptions }) => {
178+
const redis = createRedisClient("test:rateLimiter", redisOptions);
179+
180+
try {
181+
const checker = new RedisEventRateLimitChecker(createUpstashAdapter(redis));
182+
const config = { limit: 2, window: "10s" };
183+
184+
await checker.check("redis-key-2", config);
185+
await checker.check("redis-key-2", config);
186+
187+
const result = await checker.check("redis-key-2", config);
188+
expect(result.allowed).toBe(false);
189+
expect(result.remaining).toBe(0);
190+
} finally {
191+
redis.disconnect();
192+
}
193+
}
194+
);
195+
196+
redisTest(
197+
"RedisEventRateLimitChecker isolates keys",
198+
{ timeout: 30_000 },
199+
async ({ redisOptions }) => {
200+
const redis = createRedisClient("test:rateLimiter", redisOptions);
201+
202+
try {
203+
const checker = new RedisEventRateLimitChecker(createUpstashAdapter(redis));
204+
const config = { limit: 1, window: "10s" };
205+
206+
const r1 = await checker.check("redis-key-a", config);
207+
expect(r1.allowed).toBe(true);
208+
209+
const r2 = await checker.check("redis-key-b", config);
210+
expect(r2.allowed).toBe(true);
211+
212+
const r3 = await checker.check("redis-key-a", config);
213+
expect(r3.allowed).toBe(false);
214+
} finally {
215+
redis.disconnect();
216+
}
217+
}
218+
);

0 commit comments

Comments
 (0)