secure-random-password
Version:
Generate passwords using a cryptographically-strong source of randomness
138 lines (102 loc) • 4.1 kB
JavaScript
const Random = require('./random').Random;
describe('Random', () => {
let randomSource;
let subject;
beforeEach(() => {
randomSource = jest.fn();
subject = new Random(randomSource);
});
it('throws an error when not passed a function', () => {
expect(() => new Random(123)).toThrow('Must pass a randomSource function');
});
describe('getInt', () => {
it('throws an error when not passed an upper bound', () => {
expect(() => subject.getInt()).toThrow('Must pass an upper bound');
});
it('throws an error when upper bound is not a number', () => {
expect(() => subject.getInt('derp')).toThrow('Upper bound must be a number');
});
it('throws an error when upper bound is less than 1', () => {
expect(() => subject.getInt(0)).toThrow('Upper bound must be > 0');
});
it('does not call randomSource when called with an upper bound of 1', () => {
expect(subject.getInt(1)).toBe(0);
expect(randomSource).not.toHaveBeenCalled();
});
it('returns the result of randomSource(1) when upper bound is 2^8', () => {
let randomValues = [123];
randomSource.mockImplementation(n => randomValues.splice(0, n));
expect(subject.getInt(Math.pow(2, 8))).toBe(123);
});
it('returns the integer value of randomSource(4) when upper bound is 2^32', () => {
testCase([123, 45, 67, 89], 123*256*256*256 + 45*256*256 + 67*256 + 89);
testCase([0, 0, 0, 0], 0);
testCase([1, 0, 0, 0], Math.pow(2, 24));
testCase([255, 255, 255, 255], Math.pow(2, 32) - 1);
function testCase(randomValues, expected) {
randomSource.mockImplementation(n => randomValues.splice(0, n));
expect(subject.getInt(Math.pow(2, 32))).toBe(expected);
}
});
it('returns the value of randomSource modulo the upper bound', () => {
let randomValues = [123];
randomSource.mockImplementation(n => randomValues.splice(0, n));
expect(subject.getInt(100)).toBe(23);
});
it('keeps calling randomSource until the modulo is unbiased', () => {
testCase(100, [234, 234, 123], 23);
testCase(100, [199], 99);
testCase(100, [200, 123], 23);
testCase(256, [255], 255);
testCase(128, [255], 127);
function testCase(upperBound, randomValues, expected) {
randomSource.mockImplementation(n => randomValues.splice(0, n));
expect(subject.getInt(upperBound)).toBe(expected);
}
});
});
describe('choose', () => {
it('throws an error when no choices are passed', () => {
testCase(null);
testCase('');
testCase([]);
function testCase(choices) {
expect(() => subject.choose(choices)).toThrow('Must pass 1 or more choices');
}
});
it('returns the choice at the index returned by _getInt(choices.length)', () => {
let randomValues = [1];
randomSource.mockImplementation(n => randomValues.splice(0, n));
let getIntReturnValues = {
['abc'.length]: 1
};
jest.spyOn(subject, '_getInt').mockImplementation(n => getIntReturnValues[n]);
expect(subject.choose('abc')).toBe('b');
});
});
describe('shuffle', () => {
it('returns an empty array when passed nothing', () => {
expect(subject.shuffle()).toEqual([]);
});
it('does not modify the passed items array', () => {
let someItems = [1, 2, 3];
subject.shuffle(someItems);
expect(someItems).toEqual([1, 2, 3]);
});
it('returns the single item if there is only 1', () => {
let singleItem = ['a'];
expect(subject.shuffle(singleItem)).toEqual(['a']);
});
it('shuffles items by getting the order from calling _getInt', () => {
let getIntReturnValues = {
4: 1, // a _b_ cd
3: 2, // ac _d_
2: 0, // _a_ c
1: 0 // _c_
};
jest.spyOn(subject, '_getInt').mockImplementation(n => getIntReturnValues[n]);
let someItems = ['a', 'b', 'c', 'd'];
expect(subject.shuffle(someItems)).toEqual(['b', 'd', 'a', 'c']);
});
});
});