392 lines
12 KiB
JavaScript
392 lines
12 KiB
JavaScript
|
|
/* eslint-disable no-unused-expressions */
|
||
|
|
const { describe, it } = require('mocha');
|
||
|
|
const { expect } = require('chai');
|
||
|
|
const RateLimiterMemory = require('../lib/RateLimiterMemory');
|
||
|
|
|
||
|
|
describe('RateLimiterMemory with fixed window', function RateLimiterMemoryTest() {
|
||
|
|
this.timeout(5000);
|
||
|
|
|
||
|
|
it('consume 1 point', (done) => {
|
||
|
|
const testKey = 'consume1';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 2, duration: 5 });
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
const res = rateLimiterMemory._memoryStorage.get(rateLimiterMemory.getKey(testKey));
|
||
|
|
expect(res.consumedPoints).to.equal(1);
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('can not consume more than maximum points', (done) => {
|
||
|
|
const testKey = 'consume2';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 5 });
|
||
|
|
rateLimiterMemory.consume(testKey, 2)
|
||
|
|
.then(() => {})
|
||
|
|
.catch((rejRes) => {
|
||
|
|
expect(rejRes.msBeforeNext >= 0).to.equal(true);
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('execute evenly over duration with minimum delay 20 ms', (done) => {
|
||
|
|
const testKey = 'consumeEvenlyMinDelay';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({
|
||
|
|
points: 100, duration: 1, execEvenly: true, execEvenlyMinDelayMs: 20,
|
||
|
|
});
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
const timeFirstConsume = Date.now();
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
expect(Date.now() - timeFirstConsume >= 20).to.equal(true);
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('execute evenly over duration', (done) => {
|
||
|
|
const testKey = 'consumeEvenly';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({
|
||
|
|
points: 2, duration: 5, execEvenly: true, execEvenlyMinDelayMs: 1,
|
||
|
|
});
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
const timeFirstConsume = Date.now();
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
/* Second consume should be delayed more than 2 seconds
|
||
|
|
Explanation:
|
||
|
|
1) consume at 0ms, remaining duration = 5000ms
|
||
|
|
2) delayed consume for (4999 / (0 + 2)) ~= 2500ms, where 2 is a fixed value
|
||
|
|
, because it mustn't delay in the beginning and in the end of duration
|
||
|
|
3) consume after 2500ms by timeout
|
||
|
|
*/
|
||
|
|
const diff = Date.now() - timeFirstConsume;
|
||
|
|
expect(diff > 2400 && diff < 2600).to.equal(true);
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('makes penalty', (done) => {
|
||
|
|
const testKey = 'penalty1';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 3, duration: 5 });
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
rateLimiterMemory.penalty(testKey)
|
||
|
|
.then(() => {
|
||
|
|
const res = rateLimiterMemory._memoryStorage.get(rateLimiterMemory.getKey(testKey));
|
||
|
|
expect(res.consumedPoints).to.equal(2);
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('reward points', (done) => {
|
||
|
|
const testKey = 'reward1';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 5 });
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
rateLimiterMemory.reward(testKey)
|
||
|
|
.then(() => {
|
||
|
|
const res = rateLimiterMemory._memoryStorage.get(rateLimiterMemory.getKey(testKey));
|
||
|
|
expect(res.consumedPoints).to.equal(0);
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('use keyPrefix from options', () => {
|
||
|
|
const testKey = 'key';
|
||
|
|
const keyPrefix = 'test';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ keyPrefix, points: 1, duration: 5 });
|
||
|
|
|
||
|
|
expect(rateLimiterMemory.getKey(testKey)).to.equal('test:key');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('blocks key for block duration when consumed more than points', (done) => {
|
||
|
|
const testKey = 'block';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 1, blockDuration: 2 });
|
||
|
|
rateLimiterMemory.consume(testKey, 2)
|
||
|
|
.then(() => {
|
||
|
|
done(Error('must not resolve'));
|
||
|
|
})
|
||
|
|
.catch((rej) => {
|
||
|
|
expect(rej.msBeforeNext > 1000).to.equal(true);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('do not block key second time until block expires no matter how many points consumed', (done) => {
|
||
|
|
const testKey = 'donotblocktwice';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 1, blockDuration: 2 });
|
||
|
|
rateLimiterMemory.consume(testKey, 2)
|
||
|
|
.then(() => {
|
||
|
|
done(Error('must not resolve'));
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
setTimeout(() => {
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
done(Error('must not resolve'));
|
||
|
|
})
|
||
|
|
.catch((rej) => {
|
||
|
|
expect(rej.msBeforeNext < 1000).to.equal(true);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
}, 1001);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('block expires in blockDuration seconds', (done) => {
|
||
|
|
const testKey = 'blockexpires';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 1, blockDuration: 2 });
|
||
|
|
rateLimiterMemory.consume(testKey, 2)
|
||
|
|
.then(() => {
|
||
|
|
done(Error('must not resolve'));
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
setTimeout(() => {
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.consumedPoints).to.equal(1);
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
done(Error('must resolve'));
|
||
|
|
});
|
||
|
|
}, 2000);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('block custom key', (done) => {
|
||
|
|
const testKey = 'blockcustom';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 1 });
|
||
|
|
rateLimiterMemory.block(testKey, 2);
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
done(Error('must not resolve'));
|
||
|
|
})
|
||
|
|
.catch((rej) => {
|
||
|
|
expect(rej.msBeforeNext > 1000 && rej.msBeforeNext <= 2000).to.equal(true);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('get by key', (done) => {
|
||
|
|
const testKey = 'get';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 2, duration: 5 });
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
rateLimiterMemory.get(testKey)
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.remainingPoints).to.equal(1);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
done(Error('must not reject'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('get resolves null if key is not set', (done) => {
|
||
|
|
const testKey = 'getbynotexistingkey';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 2, duration: 5 });
|
||
|
|
rateLimiterMemory.get(testKey)
|
||
|
|
.then((res) => {
|
||
|
|
expect(res).to.equal(null);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('delete resolves true if key is set', (done) => {
|
||
|
|
const testKey = 'deletekey';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 2, duration: 5 });
|
||
|
|
rateLimiterMemory.consume(testKey)
|
||
|
|
.then(() => {
|
||
|
|
rateLimiterMemory.delete(testKey)
|
||
|
|
.then((res) => {
|
||
|
|
expect(res).to.equal(true);
|
||
|
|
done();
|
||
|
|
}).catch(() => {
|
||
|
|
done(Error('must not reject'));
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
done(Error('must not reject'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('delete resolves false if key is not set', (done) => {
|
||
|
|
const testKey = 'deletekey2';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 2, duration: 5 });
|
||
|
|
rateLimiterMemory.delete(testKey)
|
||
|
|
.then((res) => {
|
||
|
|
expect(res).to.equal(false);
|
||
|
|
done();
|
||
|
|
}).catch(() => {
|
||
|
|
done(Error('must not reject'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('consume applies options.customDuration to set expire', (done) => {
|
||
|
|
const testKey = 'options.customDuration';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 5 });
|
||
|
|
rateLimiterMemory.consume(testKey, 1, { customDuration: 1 })
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.msBeforeNext <= 1000).to.be.true;
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
done(Error('must not reject'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('consume applies options.customDuration to set not expiring key', (done) => {
|
||
|
|
const testKey = 'options.customDuration.forever';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 5 });
|
||
|
|
rateLimiterMemory.consume(testKey, 1, { customDuration: 0 })
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.msBeforeNext === -1).to.be.true;
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
done(Error('must not reject'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('penalty applies options.customDuration to set expire', (done) => {
|
||
|
|
const testKey = 'options.customDuration';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 5 });
|
||
|
|
rateLimiterMemory.penalty(testKey, 1, { customDuration: 1 })
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.msBeforeNext <= 1000).to.be.true;
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
done(Error('must not reject'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('reward applies options.customDuration to set expire', (done) => {
|
||
|
|
const testKey = 'options.customDuration';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 5 });
|
||
|
|
rateLimiterMemory.reward(testKey, 1, { customDuration: 1 })
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.msBeforeNext <= 1000).to.be.true;
|
||
|
|
done();
|
||
|
|
})
|
||
|
|
.catch(() => {
|
||
|
|
done(Error('must not reject'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('does not expire key if duration set to 0', (done) => {
|
||
|
|
const testKey = 'neverexpire';
|
||
|
|
const rateLimiterMemory = new RateLimiterMemory({ points: 2, duration: 0 });
|
||
|
|
rateLimiterMemory.consume(testKey, 1)
|
||
|
|
.then(() => {
|
||
|
|
rateLimiterMemory.consume(testKey, 1)
|
||
|
|
.then(() => {
|
||
|
|
rateLimiterMemory.get(testKey)
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.consumedPoints).to.equal(2);
|
||
|
|
expect(res.msBeforeNext).to.equal(-1);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('block key forever, if secDuration is 0', (done) => {
|
||
|
|
const testKey = 'neverexpire';
|
||
|
|
const rateLimiter = new RateLimiterMemory({ points: 1, duration: 1 });
|
||
|
|
rateLimiter.block(testKey, 0)
|
||
|
|
.then(() => {
|
||
|
|
setTimeout(() => {
|
||
|
|
rateLimiter.get(testKey)
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.consumedPoints).to.equal(2);
|
||
|
|
expect(res.msBeforeNext).to.equal(-1);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
}, 1000);
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('set points by key', (done) => {
|
||
|
|
const testKey = 'set';
|
||
|
|
const rateLimiter = new RateLimiterMemory({ points: 10, duration: 1 });
|
||
|
|
rateLimiter.set(testKey, 12)
|
||
|
|
.then(() => {
|
||
|
|
rateLimiter.get(testKey)
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.consumedPoints).to.equal(12);
|
||
|
|
expect(res.remainingPoints).to.equal(0);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('set points by key forever', (done) => {
|
||
|
|
const testKey = 'setforever';
|
||
|
|
const rateLimiter = new RateLimiterMemory({ points: 10, duration: 1 });
|
||
|
|
rateLimiter.set(testKey, 12, 0)
|
||
|
|
.then(() => {
|
||
|
|
setTimeout(() => {
|
||
|
|
rateLimiter.get(testKey)
|
||
|
|
.then((res) => {
|
||
|
|
expect(res.consumedPoints).to.equal(12);
|
||
|
|
expect(res.msBeforeNext).to.equal(-1);
|
||
|
|
done();
|
||
|
|
});
|
||
|
|
}, 1100);
|
||
|
|
})
|
||
|
|
.catch((err) => {
|
||
|
|
done(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|