Skip to content

Commit

Permalink
test: add get rate limiter tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexey-yarmosh committed Sep 27, 2024
1 parent 9d41d8b commit 23b8d00
Showing 1 changed file with 65 additions and 16 deletions.
81 changes: 65 additions & 16 deletions test/tests/integration/ratelimit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import requestIp from 'request-ip';
import { expect } from 'chai';
import { getTestServer, addFakeProbe, deleteFakeProbes, waitForProbesUpdate } from '../../utils/server.js';
import nockGeoIpProviders from '../../utils/nock-geo-ip.js';
import { anonymousRateLimiter, authenticatedRateLimiter } from '../../../src/lib/rate-limiter/rate-limiter-post.js';
import { anonymousRateLimiter as anonymousPostRateLimiter, authenticatedRateLimiter as authenticatedPostRateLimiter } from '../../../src/lib/rate-limiter/rate-limiter-post.js';
import { anonymousRateLimiter as anonymousGetRateLimiter, authenticatedRateLimiter as authenticatedGetRateLimiter } from '../../../src/lib/rate-limiter/rate-limiter-get.js';
import { client } from '../../../src/lib/sql/client.js';
import { GP_TOKENS_TABLE } from '../../../src/lib/http/auth.js';
import { CREDITS_TABLE } from '../../../src/lib/credits.js';
import { getPersistentRedisClient } from '../../../src/lib/redis/persistent-client.js';

describe('rate limiter', () => {
let app: Server;
let requestAgent: any;
let clientIpv6: string;
const redis = getPersistentRedisClient();

before(async () => {
app = await getTestServer();
Expand Down Expand Up @@ -46,8 +49,15 @@ describe('rate limiter', () => {


afterEach(async () => {
await anonymousRateLimiter.delete(clientIpv6);
await authenticatedRateLimiter.delete('89da69bd-a236-4ab7-9c5d-b5f52ce09959');
const [ anonGetKeys, authGetKeys ] = await Promise.all([
await redis.keys(`rate:get:anon:${clientIpv6}:*`),
await redis.keys('rate:get:auth:89da69bd-a236-4ab7-9c5d-b5f52ce09959:*'),
await anonymousPostRateLimiter.delete(clientIpv6),
await authenticatedPostRateLimiter.delete('89da69bd-a236-4ab7-9c5d-b5f52ce09959'),
]);

const getKeys = [ ...anonGetKeys, ...authGetKeys ];
getKeys.length && await redis.del(getKeys);
});

after(async () => {
Expand Down Expand Up @@ -155,11 +165,23 @@ describe('rate limiter', () => {
expect(response2.headers['x-ratelimit-reset']).to.equal('3600');
expect(response.headers['x-request-cost']).to.equal('1');
});

it('should NOT include headers (GET measurement)', async () => {
const { body: { id } } = await requestAgent.post('/v1/measurements')
.send({
type: 'ping',
target: 'jsdelivr.com',
}).expect(202) as Response;
await anonymousGetRateLimiter.set(`${clientIpv6}:${id}`, 999, 0);
const response = await requestAgent.get(`/v1/measurements/${id}`).send().expect(200) as Response;

expect(response.headers['Retry-After']).to.not.exist;
});
});

describe('anonymous access', () => {
it('should succeed (limit not reached)', async () => {
await anonymousRateLimiter.set(clientIpv6, 0, 0);
await anonymousPostRateLimiter.set(clientIpv6, 0, 0);

const response = await requestAgent.post('/v1/measurements').send({
type: 'ping',
Expand All @@ -170,7 +192,7 @@ describe('rate limiter', () => {
});

it('should fail (limit reached)', async () => {
await anonymousRateLimiter.set(clientIpv6, 100000, 0);
await anonymousPostRateLimiter.set(clientIpv6, 100000, 0);

const response = await requestAgent.post('/v1/measurements').send({
type: 'ping',
Expand All @@ -181,7 +203,7 @@ describe('rate limiter', () => {
});

it('should consume all points successfully or none at all (cost > remaining > 0)', async () => {
await anonymousRateLimiter.set(clientIpv6, 99999, 0); // 1 remaining
await anonymousPostRateLimiter.set(clientIpv6, 99999, 0); // 1 remaining

const response = await requestAgent.post('/v1/measurements').send({
type: 'ping',
Expand All @@ -191,11 +213,23 @@ describe('rate limiter', () => {

expect(response.headers['x-ratelimit-remaining']).to.equal('1');
});

it('should fail and include Retry-After header (GET measurement)', async () => {
const { body: { id } } = await requestAgent.post('/v1/measurements')
.send({
type: 'ping',
target: 'jsdelivr.com',
}).expect(202) as Response;
await anonymousGetRateLimiter.set(`${clientIpv6}:${id}`, 1000, 0);
const response = await requestAgent.get(`/v1/measurements/${id}`).send().expect(429) as Response;

expect(response.headers['retry-after']).to.equal('2');
});
});

describe('authenticated access', () => {
it('should succeed (limit not reached)', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 0, 0);
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 0, 0);

const response = await requestAgent.post('/v1/measurements')
.set('Authorization', 'Bearer qz5kdukfcr3vggv3xbujvjwvirkpkkpx')
Expand All @@ -208,7 +242,7 @@ describe('rate limiter', () => {
});

it('should fail (limit reached)', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 250, 0);
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 250, 0);

const response = await requestAgent.post('/v1/measurements')
.set('Authorization', 'Bearer qz5kdukfcr3vggv3xbujvjwvirkpkkpx')
Expand All @@ -221,7 +255,7 @@ describe('rate limiter', () => {
});

it('should consume all points successfully or none at all (cost > remaining > 0)', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 249, 0); // 1 remaining
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 249, 0); // 1 remaining

const response = await requestAgent.post('/v1/measurements')
.set('Authorization', 'Bearer qz5kdukfcr3vggv3xbujvjwvirkpkkpx')
Expand All @@ -233,6 +267,21 @@ describe('rate limiter', () => {

expect(response.headers['x-ratelimit-remaining']).to.equal('1');
});

it('should fail and include Retry-After header (GET measurement)', async () => {
const { body: { id } } = await requestAgent.post('/v1/measurements')
.send({
type: 'ping',
target: 'jsdelivr.com',
}).expect(202) as Response;
console.log(id);
await authenticatedGetRateLimiter.set(`89da69bd-a236-4ab7-9c5d-b5f52ce09959:${id}`, 1000, 0);
const response = await requestAgent.get(`/v1/measurements/${id}`)
.set('Authorization', 'Bearer qz5kdukfcr3vggv3xbujvjwvirkpkkpx')
.send().expect(429) as Response;

expect(response.headers['retry-after']).to.equal('2');
});
});

describe('access with credits', () => {
Expand All @@ -246,7 +295,7 @@ describe('rate limiter', () => {
});

it('should consume free credits before paid credits', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 0, 0);
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 0, 0);

const response = await requestAgent.post('/v1/measurements')
.set('Authorization', 'Bearer qz5kdukfcr3vggv3xbujvjwvirkpkkpx')
Expand All @@ -266,7 +315,7 @@ describe('rate limiter', () => {
});

it('should consume credits after limit is reached', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 250, 0);
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 250, 0);

const response = await requestAgent.post('/v1/measurements')
.set('Authorization', 'Bearer qz5kdukfcr3vggv3xbujvjwvirkpkkpx')
Expand All @@ -286,7 +335,7 @@ describe('rate limiter', () => {
});

it('should consume part from free credits and part from paid credits if possible', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 249, 0);
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 249, 0);

const response = await requestAgent.post('/v1/measurements')
.set('Authorization', 'Bearer qz5kdukfcr3vggv3xbujvjwvirkpkkpx')
Expand All @@ -306,7 +355,7 @@ describe('rate limiter', () => {
});

it('should not consume paid credits if there are not enough to satisfy the request', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 250, 0);
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 250, 0);

await client(CREDITS_TABLE).update({
amount: 1,
Expand All @@ -332,7 +381,7 @@ describe('rate limiter', () => {
});

it('should not consume more paid credits than the cost of the full request', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 255, 0);
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 255, 0);

const response = await requestAgent.post('/v1/measurements')
.set('Authorization', 'Bearer qz5kdukfcr3vggv3xbujvjwvirkpkkpx')
Expand All @@ -352,7 +401,7 @@ describe('rate limiter', () => {
});

it('should not consume free credits if there are not enough to satisfy the request', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 249, 0);
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 249, 0);

await client(CREDITS_TABLE).update({
amount: 0,
Expand All @@ -378,7 +427,7 @@ describe('rate limiter', () => {
});

it('should work fine if there is no credits row for that user', async () => {
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 250, 0);
await authenticatedPostRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 250, 0);

await client(CREDITS_TABLE).where({
user_id: '89da69bd-a236-4ab7-9c5d-b5f52ce09959',
Expand Down

0 comments on commit 23b8d00

Please sign in to comment.