auth-rate-limit.test.ts57 lines · main
1import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2
3/**
4 * S6.2 — rate-limit reliability (memory backend).
5 * Forces the in-memory path by clearing BRIVEN_REDIS_URL so we can assert
6 * deny-after-N and per-project isolation without a live Redis.
7 */
8
9const ORIGINAL_REDIS = process.env.BRIVEN_REDIS_URL;
10
11beforeEach(() => {
12 delete process.env.BRIVEN_REDIS_URL;
13});
14
15afterEach(() => {
16 if (ORIGINAL_REDIS === undefined) delete process.env.BRIVEN_REDIS_URL;
17 else process.env.BRIVEN_REDIS_URL = ORIGINAL_REDIS;
18});
19
20describe('auth rate limit — memory backend (S6.2)', () => {
21 test('allows under maxAttempts then denies', async () => {
22 // Dynamic import after env tweak so getRedis() sees no URL.
23 const { checkIpRateLimit } = await import('./auth-rate-limit.js');
24 const { resetAuthReliabilityCountersForTests } = await import('./auth-reliability.js');
25 resetAuthReliabilityCountersForTests();
26
27 const projectId = `p_rl_${Date.now()}`;
28 const ip = `203.0.113.${Math.floor(Math.random() * 200) + 1}`;
29 const opts = { maxAttempts: 3, windowMinutes: 5 };
30
31 const a1 = await checkIpRateLimit(projectId, ip, opts);
32 const a2 = await checkIpRateLimit(projectId, ip, opts);
33 const a3 = await checkIpRateLimit(projectId, ip, opts);
34 const a4 = await checkIpRateLimit(projectId, ip, opts);
35
36 expect(a1.allowed).toBe(true);
37 expect(a2.allowed).toBe(true);
38 expect(a3.allowed).toBe(true);
39 expect(a4.allowed).toBe(false);
40 expect(a4.retryAfterSeconds).toBeGreaterThan(0);
41 });
42
43 test('buckets are isolated per projectId (cross-project cannot share limit)', async () => {
44 const { checkIpRateLimit } = await import('./auth-rate-limit.js');
45 const opts = { maxAttempts: 2, windowMinutes: 5 };
46 const ip = '198.51.100.50';
47 const a = `p_iso_a_${Date.now()}`;
48 const b = `p_iso_b_${Date.now()}`;
49
50 expect((await checkIpRateLimit(a, ip, opts)).allowed).toBe(true);
51 expect((await checkIpRateLimit(a, ip, opts)).allowed).toBe(true);
52 expect((await checkIpRateLimit(a, ip, opts)).allowed).toBe(false);
53
54 // Project B still has a full budget for the same IP.
55 expect((await checkIpRateLimit(b, ip, opts)).allowed).toBe(true);
56 });
57});