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.
108 lines
3.0 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
});
|