Redis ratelimits
This commit is contained in:
+24
-2
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createRateLimiter } from '../src/rate-limit.js';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { createRateLimiter, createRedisRateLimiter } from '../src/rate-limit.js';
|
||||
|
||||
describe('rate limiter', () => {
|
||||
it('enforces request and spend windows', () => {
|
||||
@@ -23,4 +23,26 @@ describe('rate limiter', () => {
|
||||
expect(limiter.check('a').allowed).toBe(false);
|
||||
expect(limiter.check('b').allowed).toBe(true);
|
||||
});
|
||||
|
||||
it('uses Redis scripts and namespaces keys by client IP', async () => {
|
||||
const client = { eval: vi.fn().mockResolvedValueOnce([1]).mockResolvedValueOnce(1) };
|
||||
const limiter = createRedisRateLimiter({ client, now: () => 10_000, keyPrefix: 'test:limits' });
|
||||
|
||||
await expect(limiter.check('2001:db8::1')).resolves.toEqual({ allowed: true });
|
||||
await limiter.recordCost('2001:db8::1', 1.25);
|
||||
|
||||
expect(client.eval).toHaveBeenCalledTimes(2);
|
||||
expect(client.eval.mock.calls[0][1].keys).toEqual([
|
||||
'test:limits:{2001%3Adb8%3A%3A1}:requests',
|
||||
'test:limits:{2001%3Adb8%3A%3A1}:spend',
|
||||
'test:limits:{2001%3Adb8%3A%3A1}:sequence',
|
||||
]);
|
||||
expect(client.eval.mock.calls[1][1].arguments).toEqual(['10000', '-17990000', '1.25', '18000000']);
|
||||
});
|
||||
|
||||
it('converts Redis denials into rate-limit responses', async () => {
|
||||
const client = { eval: vi.fn().mockResolvedValue([0, 'spend', 1_001]) };
|
||||
const limiter = createRedisRateLimiter({ client, now: () => 10_000 });
|
||||
await expect(limiter.check('ip')).resolves.toEqual({ allowed: false, reason: 'spend', retryAfter: 2 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user