Files
shields/lib/token-pool.spec.js
Paul Melnikow b3ec3e7a96 Generalized rotating token pool (#1736)
This was designed as part of a rewrite of the Github auth code in #1205 which is stalled because I don't want to deploy it without access to server logs. The need for token rotation came up recently in #541, so I picked up this code, removed the github-specific bits, and pulled it in. Ordinarily I wouldn't merge helper code without a use, though sadly it seems like the best way to move forward this rewrite.
2018-07-11 23:08:56 -04:00

108 lines
3.0 KiB
JavaScript

'use strict';
const { expect } = require('chai');
const sinon = require('sinon');
const times = require('lodash.times');
const { Token, TokenPool } = require('./token-pool');
function expectPoolToBeExhausted (pool) {
expect(() => {
pool.next();
}).to.throw(Error, /^Token pool is exhausted$/);
}
describe('The token pool', function () {
const ids = [1, 2, 3, 4, 5];
const batchSize = 3;
let tokenPool;
beforeEach(function () {
// Set up.
tokenPool = new TokenPool(batchSize);
ids.forEach(id => tokenPool.add(id));
});
it('should yield the expected tokens', function () {
ids.forEach(id => times(batchSize, () => expect(tokenPool.next().id).to.equal(id)));
});
it('should repeat when reaching the end', function () {
ids.forEach(id => times(batchSize, () => expect(tokenPool.next().id).to.equal(id)));
ids.forEach(id => times(batchSize, () => expect(tokenPool.next().id).to.equal(id)));
});
context('tokens are marked exhausted immediately', function () {
it('should be exhausted', function () {
ids.forEach(() => {
const token = tokenPool.next();
token.update(0, Token.nextResetNever);
});
expectPoolToBeExhausted(tokenPool);
});
});
context('tokens are marked after the last request', function () {
it('should be exhausted', function () {
ids.forEach(() => {
const token = times(batchSize, () => tokenPool.next()).pop();
token.update(0, Token.nextResetNever);
});
expectPoolToBeExhausted(tokenPool);
});
});
context('tokens are renewed', function () {
it('should keep using them', function () {
const tokensToRenew = [2, 4];
const renewalCount = 3;
ids.forEach(id => {
const token = times(batchSize, () => tokenPool.next()).pop();
const usesRemaining = tokensToRenew.includes(token.id) ? renewalCount : 0;
token.update(usesRemaining, Token.nextResetNever);
});
tokensToRenew.forEach(id => {
let token;
times(renewalCount, () => {
token = tokenPool.next();
expect(token.id).to.equal(id);
}).pop();
token.update(0, Token.nextResetNever);
});
expectPoolToBeExhausted(tokenPool);
});
});
context('tokens reset', function () {
let clock;
beforeEach(function () { clock = sinon.useFakeTimers(); });
afterEach(function () { clock.restore(); });
it('should start using them', function () {
const tokensToReset = [2, 4];
const futureTime = 1440;
ids.forEach(id => {
const token = times(batchSize, () => tokenPool.next()).pop();
const nextReset = tokensToReset.includes(token.id) ? futureTime : Token.nextResetNever;
token.update(0, nextReset);
});
expectPoolToBeExhausted(tokenPool);
clock.tick(1000 * futureTime);
tokensToReset.forEach(id => {
const token = times(batchSize, () => tokenPool.next()).pop();
token.update(0, Token.nextResetNever);
});
expectPoolToBeExhausted(tokenPool);
});
});
});