File size: 2,246 Bytes
077865a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import type { Express } from 'express';
import { createApp } from '../../app.js';
import { initDb } from '../../db/index.js';

async function request(app: Express, headers: Record<string, string> = {}) {
  const server = app.listen(0);
  const addr = server.address() as any;
  const url = `http://127.0.0.1:${addr.port}/v1/chat/completions`;

  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', ...headers },
    body: JSON.stringify({ messages: [{ role: 'user', content: 'hi' }] }),
  });

  const text = await res.text();
  server.close();

  let json: any = null;
  try { json = JSON.parse(text); } catch {}

  return { status: res.status, body: json, headers: res.headers };
}

describe('Proxy per-IP rate limiting (#35 item #6)', () => {
  const originalRpm = process.env.PROXY_RATE_LIMIT_RPM;

  beforeAll(() => {
    process.env.ENCRYPTION_KEY = '0'.repeat(64);
    initDb(':memory:');
  });

  afterAll(() => {
    if (originalRpm === undefined) delete process.env.PROXY_RATE_LIMIT_RPM;
    else process.env.PROXY_RATE_LIMIT_RPM = originalRpm;
  });

  it('returns 429 once the per-minute cap is exceeded', async () => {
    // Limiter reads the env at createApp() time, so set it before building.
    process.env.PROXY_RATE_LIMIT_RPM = '3';
    const app = createApp();

    // First 3 requests pass the limiter (they 401 on auth, but the limiter is
    // upstream of auth, so each one still counts against the window).
    for (let i = 0; i < 3; i++) {
      const res = await request(app);
      expect(res.status).not.toBe(429);
    }

    // The 4th trips the limit.
    const limited = await request(app);
    expect(limited.status).toBe(429);
    expect(limited.body.error.type).toBe('rate_limit_error');
    expect(limited.headers.get('retry-after')).toBeTruthy();
    expect(limited.headers.get('x-ratelimit-limit')).toBe('3');
  });

  it('does not rate-limit when PROXY_RATE_LIMIT_RPM=0', async () => {
    process.env.PROXY_RATE_LIMIT_RPM = '0';
    const app = createApp();

    for (let i = 0; i < 5; i++) {
      const res = await request(app);
      expect(res.status).not.toBe(429);
    }
  });
});