/* eslint-env mocha */ /* eslint-disable no-unused-expressions */ /* eslint-disable security/detect-object-injection */ const cluster = require('cluster'); const sinon = require('sinon'); const { describe, it, after } = require('mocha'); const { expect } = require('chai'); const { RateLimiterClusterMaster, RateLimiterCluster } = require('../lib/RateLimiterCluster'); const masterEvents = []; const workerEvents = []; const worker = { send: (data) => { workerEvents.forEach((cb) => { cb(data); }); }, }; global.process.on = (eventName, cb) => { if (eventName === 'message') { workerEvents.push(cb); } }; global.process.send = (data) => { masterEvents.forEach((cb) => { cb(worker, data); }); }; describe('RateLimiterCluster', function RateLimiterClusterTest() { let rateLimiterClusterMaster; let clusterStubOn; this.timeout(5000); before(() => { clusterStubOn = sinon.stub(cluster, 'on').callsFake((eventName, cb) => { masterEvents.push(cb); }); rateLimiterClusterMaster = new RateLimiterClusterMaster(); }); after(() => { clusterStubOn.restore(); }); it('master must be singleton', () => { const rateLimiterClusterMaster2 = new RateLimiterClusterMaster(); expect(rateLimiterClusterMaster2 === rateLimiterClusterMaster).to.equal(true); }); it('consume 1 point', (done) => { const key = 'consume1'; const rateLimiterCluster = new RateLimiterCluster({ points: 2, duration: 5, keyPrefix: key }); rateLimiterCluster.consume(key) .then((res) => { expect(res.remainingPoints).to.equal(1); done(); }) .catch((rej) => { done(rej); }); }); it('reject on consuming more than maximum points', (done) => { const key = 'reject'; const rateLimiterCluster = new RateLimiterCluster({ points: 2, duration: 5, keyPrefix: key }); rateLimiterCluster.consume(key, 3) .then(() => { }) .catch((rejRes) => { expect(rejRes.remainingPoints).to.equal(0); done(); }); }); // it('execute evenly over duration', (done) => { const key = 'evenly'; const rateLimiterCluster = new RateLimiterCluster({ points: 2, duration: 5, execEvenly: true, keyPrefix: key, }); rateLimiterCluster.consume(key) .then(() => { const timeFirstConsume = Date.now(); rateLimiterCluster.consume(key) .then(() => { /* Second consume should be delayed more than 2 seconds Explanation: 1) consume at 0ms, remaining duration = 4444ms 2) delayed consume for (4444 / (0 + 2)) ~= 2222ms, where 2 is a fixed value , because it mustn't delay in the beginning and in the end of duration 3) consume after 2222ms by timeout */ expect((Date.now() - timeFirstConsume) > 2000).to.equal(true); done(); }) .catch((err) => { done(err); }); }) .catch((err) => { done(err); }); }); it('use keyPrefix from options', (done) => { const key = 'use keyPrefix from options'; const keyPrefix = 'test'; const rateLimiterCluster = new RateLimiterCluster({ points: 2, duration: 5, keyPrefix }); rateLimiterCluster.consume(key) .then(() => { expect(typeof rateLimiterClusterMaster._rateLimiters[keyPrefix]._memoryStorage._storage[`${keyPrefix}:${key}`] !== 'undefined').to.equal(true); done(); }) .catch((rejRes) => { done(rejRes); }); }); it('create 2 rate limiters depending on keyPrefix', (done) => { const keyPrefixes = ['create1', 'create2']; const rateLimiterClusterprocess1 = new RateLimiterCluster({ keyPrefix: keyPrefixes[0] }); const rateLimiterClusterprocess2 = new RateLimiterCluster({ keyPrefix: keyPrefixes[1] }); rateLimiterClusterprocess1.consume('key1') .then(() => { rateLimiterClusterprocess2.consume('key2') .then(() => { const createdKeyLimiters = Object.keys(rateLimiterClusterMaster._rateLimiters); expect(createdKeyLimiters.indexOf(keyPrefixes[0]) !== -1 && createdKeyLimiters.indexOf(keyPrefixes[0]) !== -1).to.equal(true); done(); }); }); }); it('penalty', (done) => { const key = 'penalty'; const rateLimiterCluster = new RateLimiterCluster({ points: 2, duration: 5, keyPrefix: key }); rateLimiterCluster.penalty(key) .then((res) => { expect(res.remainingPoints).to.equal(1); done(); }); }); it('reward', (done) => { const key = 'reward'; const rateLimiterCluster = new RateLimiterCluster({ points: 2, duration: 5, keyPrefix: key }); rateLimiterCluster.consume(key) .then(() => { rateLimiterCluster.reward(key) .then((res) => { expect(res.remainingPoints).to.equal(2); done(); }); }); }); it('block', (done) => { const key = 'block'; const rateLimiterCluster = new RateLimiterCluster({ points: 1, duration: 1, keyPrefix: key }); rateLimiterCluster.block(key, 2) .then((res) => { expect(res.msBeforeNext > 1000 && res.msBeforeNext <= 2000).to.equal(true); done(); }); }); it('get', (done) => { const key = 'get'; const rateLimiterCluster = new RateLimiterCluster({ points: 1, duration: 1, keyPrefix: key }); rateLimiterCluster.consume(key) .then(() => { rateLimiterCluster.get(key) .then((res) => { expect(res.consumedPoints).to.equal(1); done(); }); }); }); it('get null', (done) => { const key = 'getnull'; const rateLimiterCluster = new RateLimiterCluster({ points: 1, duration: 1, keyPrefix: key }); rateLimiterCluster.get(key) .then((res) => { expect(res).to.equal(null); done(); }); }); it('delete', (done) => { const key = 'deletetrue'; const rateLimiterCluster = new RateLimiterCluster({ points: 1, duration: 10, keyPrefix: key }); rateLimiterCluster.consume(key) .then(() => { rateLimiterCluster.delete(key) .then((res) => { expect(res).to.equal(true); done(); }); }); }); it('consume applies options.customDuration to set expire', (done) => { const key = 'consume.customDuration'; const rateLimiterCluster = new RateLimiterCluster({ points: 2, duration: 5, keyPrefix: key }); rateLimiterCluster.consume(key, 1, { customDuration: 1 }) .then((res) => { expect(res.msBeforeNext <= 1000).to.be.true; done(); }) .catch((rej) => { done(rej); }); }); });