Windshaft-cartodb/test/acceptance/rate-limit.test.js

361 lines
11 KiB
JavaScript
Raw Normal View History

2018-02-20 17:57:29 +08:00
require('../support/test_helper');
const assert = require('../support/assert');
const redis = require('redis');
2018-02-26 23:23:42 +08:00
const RedisPool = require('redis-mpool');
const cartodbRedis = require('cartodb-redis');
const TestClient = require('../support/test-client');
const UserLimitsApi = require('../../lib/cartodb/backends/api/user_limits_api');
const rateLimitMiddleware = require('../../lib/cartodb/api/middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimitMiddleware;
2018-02-20 17:57:29 +08:00
let userLimitsApi;
2018-02-26 23:23:42 +08:00
let rateLimit;
2018-02-20 17:57:29 +08:00
let redisClient;
let testClient;
let keysToDelete = ['user:localhost:mapviews:global'];
2018-02-26 18:20:31 +08:00
const user = 'localhost';
2018-03-23 20:30:47 +08:00
let layergroupid;
2018-02-20 17:57:29 +08:00
const query = `
SELECT
ST_Transform('SRID=4326;POINT(-180 85.05112877)'::geometry, 3857) the_geom_webmercator,
1 cartodb_id,
2 val
`;
const createMapConfig = ({
version = '1.6.0',
type = 'cartodb',
sql = query,
cartocss = TestClient.CARTOCSS.POINTS,
cartocss_version = '2.3.0',
interactivity = 'cartodb_id',
countBy = 'cartodb_id'
} = {}) => ({
version,
layers: [{
type,
options: {
source: {
id: 'a0'
},
cartocss,
cartocss_version,
interactivity
}
}],
analyses: [
{
id: 'a0',
type: 'source',
params: {
query: sql
}
}
],
dataviews: {
count: {
source: {
id: 'a0'
},
type: 'formula',
options: {
column: countBy,
operation: 'count'
}
}
}
});
2018-03-23 20:30:47 +08:00
function setLimit(count, period, burst, endpoint = RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS) {
2018-02-20 18:38:44 +08:00
redisClient.SELECT(8, err => {
2018-02-20 17:57:29 +08:00
if (err) {
return;
}
2018-03-23 20:30:47 +08:00
const key = `limits:rate:store:${user}:maps:${endpoint}`;
2018-02-23 23:23:59 +08:00
redisClient.rpush(key, burst);
redisClient.rpush(key, count);
redisClient.rpush(key, period);
keysToDelete.push(key);
2018-02-20 17:57:29 +08:00
});
}
2018-02-26 23:23:42 +08:00
function getReqAndRes() {
return {
req: {},
res: {
headers: {},
2018-03-23 17:23:57 +08:00
set(headers, value) {
if(typeof headers === 'object') {
this.headers = headers;
} else {
this.headers[headers] = value;
}
2018-02-26 23:23:42 +08:00
},
locals: {
user: 'localhost'
}
}
};
}
2018-03-23 17:23:57 +08:00
function assertGetLayergroupRequest (status, limit, remaining, reset, retry, done) {
let response = {
2018-03-03 03:52:06 +08:00
status,
headers: {
'Content-Type': 'application/json; charset=utf-8',
2018-03-14 19:15:07 +08:00
'Carto-Rate-Limit-Limit': limit,
'Carto-Rate-Limit-Remaining': remaining,
2018-03-23 17:23:57 +08:00
'Carto-Rate-Limit-Reset': reset
2018-03-03 03:52:06 +08:00
}
};
2018-03-23 17:23:57 +08:00
if(retry) {
response.headers['Retry-After'] = retry;
}
2018-03-03 03:52:06 +08:00
testClient.getLayergroup({ response }, err => {
assert.ifError(err);
if (done) {
setTimeout(done, 1000);
}
});
}
2018-03-23 17:23:57 +08:00
function assertRateLimitRequest (status, limit, remaining, reset, retry, done) {
2018-03-03 03:52:06 +08:00
const { req, res } = getReqAndRes();
rateLimit(req, res, function (err) {
2018-03-23 17:23:57 +08:00
let expectedHeaders = {
2018-03-14 19:15:07 +08:00
"Carto-Rate-Limit-Limit": limit,
"Carto-Rate-Limit-Remaining": remaining,
2018-03-23 17:23:57 +08:00
"Carto-Rate-Limit-Reset": reset
};
2018-03-23 17:23:57 +08:00
if(retry) {
expectedHeaders['Retry-After'] = retry;
}
2018-03-23 17:23:57 +08:00
assert.deepEqual(res.headers, expectedHeaders);
2018-03-03 03:52:06 +08:00
if(status === 200) {
assert.ifError(err);
} else {
assert.ok(err);
assert.equal(err.message, 'You are over platform\'s limits. Please contact us to know more details');
2018-03-03 03:52:06 +08:00
assert.equal(err.http_status, 429);
assert.equal(err.type, 'limit');
assert.equal(err.subtype, 'rate-limit');
2018-03-03 03:52:06 +08:00
}
if (done) {
setTimeout(done, 1000);
}
});
}
2018-02-26 23:23:42 +08:00
describe('rate limit', function() {
2018-02-20 17:57:29 +08:00
before(function() {
2018-02-21 00:58:08 +08:00
global.environment.enabledFeatures.rateLimitsEnabled = true;
2018-02-21 01:17:25 +08:00
global.environment.enabledFeatures.rateLimitsByEndpoint.anonymous = true;
2018-02-20 17:57:29 +08:00
redisClient = redis.createClient(global.environment.redis.port);
testClient = new TestClient(createMapConfig(), 1234);
});
2018-02-21 00:58:08 +08:00
after(function() {
global.environment.enabledFeatures.rateLimitsEnabled = false;
2018-02-21 01:17:25 +08:00
global.environment.enabledFeatures.rateLimitsByEndpoint.anonymous = false;
2018-02-21 00:58:08 +08:00
});
2018-02-20 17:57:29 +08:00
afterEach(function(done) {
keysToDelete.forEach( key => {
redisClient.del(key);
});
redisClient.SELECT(0, () => {
redisClient.del('user:localhost:mapviews:global');
redisClient.SELECT(5, () => {
redisClient.del('user:localhost:mapviews:global');
done();
});
});
});
2018-02-20 17:57:29 +08:00
it('should not be rate limited', function (done) {
const count = 1;
const period = 1;
const burst = 1;
setLimit(count, period, burst);
2018-03-23 17:23:57 +08:00
assertGetLayergroupRequest(200, '2', '1', '1', null, done);
2018-02-20 17:57:29 +08:00
});
2018-02-27 00:03:56 +08:00
it("1 req/sec: 2 req/seg should be limited", function(done) {
2018-02-20 17:57:29 +08:00
const count = 1;
const period = 1;
const burst = 1;
setLimit(count, period, burst);
2018-03-23 17:23:57 +08:00
assertGetLayergroupRequest(200, '2', '1', '1');
setTimeout( () => assertGetLayergroupRequest(200, '2', '0', '1'), 250);
setTimeout( () => assertGetLayergroupRequest(429, '2', '0', '1', '1'), 500);
setTimeout( () => assertGetLayergroupRequest(429, '2', '0', '1', '1'), 750);
setTimeout( () => assertGetLayergroupRequest(429, '2', '0', '1', '1'), 950);
setTimeout( () => assertGetLayergroupRequest(200, '2', '0', '1', null, done), 1050);
2018-02-20 17:57:29 +08:00
});
2018-02-26 23:23:42 +08:00
});
describe('rate limit middleware', function () {
before(function (done) {
2018-02-26 23:23:42 +08:00
global.environment.enabledFeatures.rateLimitsEnabled = true;
global.environment.enabledFeatures.rateLimitsByEndpoint.anonymous = true;
const redisPool = new RedisPool(global.environment.redis);
const metadataBackend = cartodbRedis({ pool: redisPool });
userLimitsApi = new UserLimitsApi(metadataBackend, {
limits: {
rateLimitsEnabled: global.environment.enabledFeatures.rateLimitsEnabled
}
});
rateLimit = rateLimitMiddleware(userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS);
2018-02-26 23:23:42 +08:00
redisClient = redis.createClient(global.environment.redis.port);
testClient = new TestClient(createMapConfig(), 1234);
const count = 1;
const period = 1;
const burst = 0;
setLimit(count, period, burst);
2018-03-03 03:52:06 +08:00
setTimeout(done, 1000);
2018-02-26 23:23:42 +08:00
});
after(function () {
global.environment.enabledFeatures.rateLimitsEnabled = false;
global.environment.enabledFeatures.rateLimitsByEndpoint.anonymous = false;
keysToDelete.forEach(key => {
redisClient.del(key);
});
});
2018-02-27 00:03:56 +08:00
it("1 req/sec: 2 req/seg should be limited", function (done) {
2018-03-23 17:23:57 +08:00
assertRateLimitRequest(200, 1, 0, 1);
setTimeout( () => assertRateLimitRequest(429, 1, 0, 0, 1), 250);
setTimeout( () => assertRateLimitRequest(429, 1, 0, 0, 1), 500);
setTimeout( () => assertRateLimitRequest(429, 1, 0, 0, 1), 750);
setTimeout( () => assertRateLimitRequest(429, 1, 0, 0, 1), 950);
setTimeout( () => assertRateLimitRequest(200, 1, 0, 1, null, done), 1050);
2018-02-26 23:23:42 +08:00
});
2018-03-03 03:52:06 +08:00
it("1 req/sec: 2 req/seg should be limited, removing SHA script from Redis", function (done) {
userLimitsApi.metadataBackend.redisCmd(
8,
'SCRIPT',
['FLUSH'],
function () {
2018-03-23 17:23:57 +08:00
assertRateLimitRequest(200, 1, 0, 1);
setTimeout( () => assertRateLimitRequest(429, 1, 0, 0, 1), 500);
setTimeout( () => assertRateLimitRequest(429, 1, 0, 0, 1), 500);
setTimeout( () => assertRateLimitRequest(429, 1, 0, 0, 1), 750);
setTimeout( () => assertRateLimitRequest(429, 1, 0, 0, 1), 950);
setTimeout( () => assertRateLimitRequest(200, 1, 0, 1, null, done), 1050);
}
);
});
2018-02-27 00:03:56 +08:00
});
2018-03-23 20:30:47 +08:00
describe('rate limit and vector tiles', function () {
before(function(done) {
global.environment.enabledFeatures.rateLimitsEnabled = true;
global.environment.enabledFeatures.rateLimitsByEndpoint.tile = true;
2018-03-23 20:30:47 +08:00
redisClient = redis.createClient(global.environment.redis.port);
const count = 1;
const period = 1;
const burst = 0;
setLimit(count, period, burst, RATE_LIMIT_ENDPOINTS_GROUPS.TILE);
testClient = new TestClient(createMapConfig(), 1234);
testClient.getLayergroup({status: 200}, (err, res) => {
assert.ifError(err);
2018-03-23 20:30:47 +08:00
layergroupid = res.layergroupid;
2018-03-23 20:30:47 +08:00
done();
});
});
after(function() {
global.environment.enabledFeatures.rateLimitsEnabled = false;
global.environment.enabledFeatures.rateLimitsByEndpoint.tile = false;
});
afterEach(function(done) {
keysToDelete.forEach( key => {
redisClient.del(key);
});
redisClient.SELECT(0, () => {
redisClient.del('user:localhost:mapviews:global');
redisClient.SELECT(5, () => {
redisClient.del('user:localhost:mapviews:global');
done();
});
});
});
it('mvt rate limited', function (done) {
const tileParams = (status, limit, remaining, reset, retry, contentType) => {
let headers = {
"Content-Type": contentType,
"Carto-Rate-Limit-Limit": limit,
"Carto-Rate-Limit-Remaining": remaining,
"Carto-Rate-Limit-Reset": reset
};
if (retry) {
headers['Retry-After'] = retry;
}
return {
layergroupid: layergroupid,
format: 'mvt',
response: {status, headers}
};
};
testClient.getTile(0, 0, 0, tileParams(204, '1', '0', '1'), (err) => {
assert.ifError(err);
2018-03-23 22:57:30 +08:00
testClient.getTile(
0,
0,
0,
tileParams(429, '1', '0', '0', '1', 'application/x-protobuf'),
2018-03-23 22:57:30 +08:00
(err, res, tile) => {
assert.ifError(err);
var tileJSON = tile.toJSON();
assert.equal(Array.isArray(tileJSON), true);
assert.equal(tileJSON.length, 2);
assert.equal(tileJSON[0].name, 'errorTileSquareLayer');
assert.equal(tileJSON[1].name, 'errorTileStripesLayer');
2018-03-23 22:57:30 +08:00
done();
}
);
2018-03-23 20:30:47 +08:00
});
2018-03-23 20:30:47 +08:00
});
});