You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

840 lines
24 KiB

/* global describe, it, before, beforeEach, afterEach */
'use strict';
var chai = require('chai'),
expect = chai.expect,
sinon = require('sinon'),
sinonChai = require('sinon-chai'),
Cache = require('./index').Cache,
cache = new Cache(),
clock;
chai.use(sinonChai);
describe('node-cache', function() {
beforeEach(function() {
clock = sinon.useFakeTimers();
cache.clear();
});
afterEach(function() {
clock.restore();
});
describe('put()', function() {
before(function() {
cache.debug(false);
});
it('should allow adding a new item to the cache', function() {
expect(function() {
cache.put('key', 'value');
}).to.not.throw();
});
it('should allow adding a new item to the cache with a timeout', function() {
expect(function() {
cache.put('key', 'value', 100);
}).to.not.throw();
});
it('should allow adding a new item to the cache with a timeout callback', function() {
expect(function() {
cache.put('key', 'value', 100, function() {});
}).to.not.throw();
});
it('should throw an error given a non-numeric timeout', function() {
expect(function() {
cache.put('key', 'value', 'foo');
}).to.throw();
});
it('should throw an error given a timeout of NaN', function() {
expect(function() {
cache.put('key', 'value', NaN);
}).to.throw();
});
it('should throw an error given a timeout of 0', function() {
expect(function() {
cache.put('key', 'value', 0);
}).to.throw();
});
it('should throw an error given a negative timeout', function() {
expect(function() {
cache.put('key', 'value', -100);
}).to.throw();
});
it('should throw an error given a non-function timeout callback', function() {
expect(function() {
cache.put('key', 'value', 100, 'foo');
}).to.throw();
});
it('should cause the timeout callback to fire once the cache item expires', function() {
var spy = sinon.spy();
cache.put('key', 'value', 1000, spy);
clock.tick(999);
expect(spy).to.not.have.been.called;
clock.tick(1);
expect(spy).to.have.been.calledOnce.and.calledWith('key', 'value');
});
it('should override the timeout callback on a new put() with a different timeout callback', function() {
var spy1 = sinon.spy();
var spy2 = sinon.spy();
cache.put('key', 'value', 1000, spy1);
clock.tick(999);
cache.put('key', 'value', 1000, spy2)
clock.tick(1001);
expect(spy1).to.not.have.been.called;
expect(spy2).to.have.been.calledOnce.and.calledWith('key', 'value');
});
it('should cancel the timeout callback on a new put() without a timeout callback', function() {
var spy = sinon.spy();
cache.put('key', 'value', 1000, spy);
clock.tick(999);
cache.put('key', 'value');
clock.tick(1);
expect(spy).to.not.have.been.called;
});
it('should return the cached value', function() {
expect(cache.put('key', 'value')).to.equal('value');
});
});
describe('del()', function() {
before(function() {
cache.debug(false);
});
it('should return false given a key for an empty cache', function() {
expect(cache.del('miss')).to.be.false;
});
it('should return false given a key not in a non-empty cache', function() {
cache.put('key', 'value');
expect(cache.del('miss')).to.be.false;
});
it('should return true given a key in the cache', function() {
cache.put('key', 'value');
expect(cache.del('key')).to.be.true;
});
it('should remove the provided key from the cache', function() {
cache.put('key', 'value');
expect(cache.get('key')).to.equal('value');
expect(cache.del('key')).to.be.true;
expect(cache.get('key')).to.be.null;
});
it('should decrement the cache size by 1', function() {
cache.put('key', 'value');
expect(cache.size()).to.equal(1);
expect(cache.del('key')).to.be.true;
expect(cache.size()).to.equal(0);
});
it('should not remove other keys in the cache', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
expect(cache.get('key1')).to.equal('value1');
expect(cache.get('key2')).to.equal('value2');
expect(cache.get('key3')).to.equal('value3');
cache.del('key1');
expect(cache.get('key1')).to.be.null;
expect(cache.get('key2')).to.equal('value2');
expect(cache.get('key3')).to.equal('value3');
});
it('should only delete a key from the cache once even if called multiple times in a row', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
expect(cache.size()).to.equal(3);
cache.del('key1');
cache.del('key1');
cache.del('key1');
expect(cache.size()).to.equal(2);
});
it('should handle deleting keys which were previously deleted and then re-added to the cache', function() {
cache.put('key', 'value');
expect(cache.get('key')).to.equal('value');
cache.del('key');
expect(cache.get('key')).to.be.null;
cache.put('key', 'value');
expect(cache.get('key')).to.equal('value');
cache.del('key');
expect(cache.get('key')).to.be.null;
});
it('should return true given an non-expired key', function() {
cache.put('key', 'value', 1000);
clock.tick(999);
expect(cache.del('key')).to.be.true;
});
it('should return false given an expired key', function() {
cache.put('key', 'value', 1000);
clock.tick(1000);
expect(cache.del('key')).to.be.false;
});
it('should cancel the timeout callback for the deleted key', function() {
var spy = sinon.spy();
cache.put('key', 'value', 1000, spy);
cache.del('key');
clock.tick(1000);
expect(spy).to.not.have.been.called;
});
it('should handle deletion of many items', function(done) {
clock.restore();
var num = 1000;
for(var i = 0; i < num; i++){
cache.put('key' + i, i, 1000);
}
expect(cache.size()).to.equal(num);
setTimeout(function(){
expect(cache.size()).to.equal(0);
done();
}, 1000);
});
});
describe('clear()', function() {
before(function() {
cache.debug(false);
});
it('should have no effect given an empty cache', function() {
expect(cache.size()).to.equal(0);
cache.clear();
expect(cache.size()).to.equal(0);
});
it('should remove all existing keys in the cache', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
expect(cache.size()).to.equal(3);
cache.clear();
expect(cache.size()).to.equal(0);
});
it('should remove the keys in the cache', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
expect(cache.get('key1')).to.equal('value1');
expect(cache.get('key2')).to.equal('value2');
expect(cache.get('key3')).to.equal('value3');
cache.clear();
expect(cache.get('key1')).to.be.null;
expect(cache.get('key2')).to.be.null;
expect(cache.get('key3')).to.be.null;
});
it('should reset the cache size to 0', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
expect(cache.size()).to.equal(3);
cache.clear();
expect(cache.size()).to.equal(0);
});
it('should reset the debug cache hits', function() {
cache.debug(true);
cache.put('key', 'value');
cache.get('key');
expect(cache.hits()).to.equal(1);
cache.clear();
expect(cache.hits()).to.equal(0);
});
it('should reset the debug cache misses', function() {
cache.debug(true);
cache.put('key', 'value');
cache.get('miss1');
expect(cache.misses()).to.equal(1);
cache.clear();
expect(cache.misses()).to.equal(0);
});
it('should cancel the timeout callbacks for all existing keys', function() {
var spy1 = sinon.spy();
var spy2 = sinon.spy();
var spy3 = sinon.spy();
cache.put('key1', 'value1', 1000, spy1);
cache.put('key2', 'value2', 1000, spy2);
cache.put('key3', 'value3', 1000, spy3);
cache.clear();
clock.tick(1000);
expect(spy1).to.not.have.been.called;
expect(spy2).to.not.have.been.called;
expect(spy3).to.not.have.been.called;
});
});
describe('get()', function() {
before(function() {
cache.debug(false);
});
it('should return null given a key for an empty cache', function() {
expect(cache.get('miss')).to.be.null;
});
it('should return null given a key not in a non-empty cache', function() {
cache.put('key', 'value');
expect(cache.get('miss')).to.be.null;
});
it('should return the corresponding value of a key in the cache', function() {
cache.put('key', 'value');
expect(cache.get('key')).to.equal('value');
});
it('should return the latest corresponding value of a key in the cache', function() {
cache.put('key', 'value1');
cache.put('key', 'value2');
cache.put('key', 'value3');
expect(cache.get('key')).to.equal('value3');
});
it('should handle various types of cache keys', function() {
var keys = [null, undefined, NaN, true, false, 0, 1, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, '', 'a', [], {}, [1, 'a', false], {a:1,b:'a',c:false}, function() {}];
keys.forEach(function(key, index) {
var value = 'value' + index;
cache.put(key, value);
expect(cache.get(key)).to.deep.equal(value);
});
});
it('should handle various types of cache values', function() {
var values = [null, undefined, NaN, true, false, 0, 1, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, '', 'a', [], {}, [1, 'a', false], {a:1,b:'a',c:false}, function() {}];
values.forEach(function(value, index) {
var key = 'key' + index;
cache.put(key, value);
expect(cache.get(key)).to.deep.equal(value);
});
});
it('should not set a timeout given no expiration time', function() {
cache.put('key', 'value');
clock.tick(1000);
expect(cache.get('key')).to.equal('value');
});
it('should return the corresponding value of a non-expired key in the cache', function() {
cache.put('key', 'value', 1000);
clock.tick(999);
expect(cache.get('key')).to.equal('value');
});
it('should return null given an expired key', function() {
cache.put('key', 'value', 1000);
clock.tick(1000);
expect(cache.get('key')).to.be.null;
});
it('should return null given an expired key', function() {
cache.put('key', 'value', 1000);
clock.tick(1000);
expect(cache.get('key')).to.be.null;
});
it('should return null given a key which is a property on the Object prototype', function() {
expect(cache.get('toString')).to.be.null;
});
it('should allow reading the value for a key which is a property on the Object prototype', function() {
cache.put('toString', 'value');
expect(cache.get('toString')).to.equal('value');
});
});
describe('size()', function() {
before(function() {
cache.debug(false);
});
it('should return 0 given a fresh cache', function() {
expect(cache.size()).to.equal(0);
});
it('should return 1 after adding a single item to the cache', function() {
cache.put('key', 'value');
expect(cache.size()).to.equal(1);
});
it('should return 3 after adding three items to the cache', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
expect(cache.size()).to.equal(3);
});
it('should not multi-count duplicate items added to the cache', function() {
cache.put('key', 'value1');
expect(cache.size()).to.equal(1);
cache.put('key', 'value2');
expect(cache.size()).to.equal(1);
});
it('should update when a key in the cache expires', function() {
cache.put('key', 'value', 1000);
expect(cache.size()).to.equal(1);
clock.tick(999);
expect(cache.size()).to.equal(1);
clock.tick(1);
expect(cache.size()).to.equal(0);
});
});
describe('memsize()', function() {
before(function() {
cache.debug(false);
});
it('should return 0 given a fresh cache', function() {
expect(cache.memsize()).to.equal(0);
});
it('should return 1 after adding a single item to the cache', function() {
cache.put('key', 'value');
expect(cache.memsize()).to.equal(1);
});
it('should return 3 after adding three items to the cache', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
expect(cache.memsize()).to.equal(3);
});
it('should not multi-count duplicate items added to the cache', function() {
cache.put('key', 'value1');
expect(cache.memsize()).to.equal(1);
cache.put('key', 'value2');
expect(cache.memsize()).to.equal(1);
});
it('should update when a key in the cache expires', function() {
cache.put('key', 'value', 1000);
expect(cache.memsize()).to.equal(1);
clock.tick(999);
expect(cache.memsize()).to.equal(1);
clock.tick(1);
expect(cache.memsize()).to.equal(0);
});
});
describe('debug()', function() {
it('should not count cache hits when false', function() {
cache.debug(false);
cache.put('key', 'value');
cache.get('key');
expect(cache.hits()).to.equal(0);
});
it('should not count cache misses when false', function() {
cache.debug(false);
cache.put('key', 'value');
cache.get('miss1');
expect(cache.misses()).to.equal(0);
});
it('should count cache hits when true', function() {
cache.debug(true);
cache.put('key', 'value');
cache.get('key');
expect(cache.hits()).to.equal(1);
});
it('should count cache misses when true', function() {
cache.debug(true);
cache.put('key', 'value');
cache.get('miss1');
expect(cache.misses()).to.equal(1);
});
});
describe('hits()', function() {
before(function() {
cache.debug(true);
});
it('should return 0 given an empty cache', function() {
expect(cache.hits()).to.equal(0);
});
it('should return 0 given a non-empty cache which has not been accessed', function() {
cache.put('key', 'value');
expect(cache.hits()).to.equal(0);
});
it('should return 0 given a non-empty cache which has had only misses', function() {
cache.put('key', 'value');
cache.get('miss1');
cache.get('miss2');
cache.get('miss3');
expect(cache.hits()).to.equal(0);
});
it('should return 1 given a non-empty cache which has had a single hit', function() {
cache.put('key', 'value');
cache.get('key');
expect(cache.hits()).to.equal(1);
});
it('should return 3 given a non-empty cache which has had three hits on the same key', function() {
cache.put('key', 'value');
cache.get('key');
cache.get('key');
cache.get('key');
expect(cache.hits()).to.equal(3);
});
it('should return 3 given a non-empty cache which has had three hits across many keys', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
cache.get('key1');
cache.get('key2');
cache.get('key3');
expect(cache.hits()).to.equal(3);
});
it('should return the correct value after a sequence of hits and misses', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
cache.get('key1');
cache.get('miss');
cache.get('key3');
expect(cache.hits()).to.equal(2);
});
it('should not count hits for expired keys', function() {
cache.put('key', 'value', 1000);
cache.get('key');
expect(cache.hits()).to.equal(1);
clock.tick(999);
cache.get('key');
expect(cache.hits()).to.equal(2);
clock.tick(1);
cache.get('key');
expect(cache.hits()).to.equal(2);
});
});
describe('misses()', function() {
before(function() {
cache.debug(true);
});
it('should return 0 given an empty cache', function() {
expect(cache.misses()).to.equal(0);
});
it('should return 0 given a non-empty cache which has not been accessed', function() {
cache.put('key', 'value');
expect(cache.misses()).to.equal(0);
});
it('should return 0 given a non-empty cache which has had only hits', function() {
cache.put('key', 'value');
cache.get('key');
cache.get('key');
cache.get('key');
expect(cache.misses()).to.equal(0);
});
it('should return 1 given a non-empty cache which has had a single miss', function() {
cache.put('key', 'value');
cache.get('miss');
expect(cache.misses()).to.equal(1);
});
it('should return 3 given a non-empty cache which has had three misses', function() {
cache.put('key', 'value');
cache.get('miss1');
cache.get('miss2');
cache.get('miss3');
expect(cache.misses()).to.equal(3);
});
it('should return the correct value after a sequence of hits and misses', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
cache.get('key1');
cache.get('miss');
cache.get('key3');
expect(cache.misses()).to.equal(1);
});
it('should count misses for expired keys', function() {
cache.put('key', 'value', 1000);
cache.get('key');
expect(cache.misses()).to.equal(0);
clock.tick(999);
cache.get('key');
expect(cache.misses()).to.equal(0);
clock.tick(1);
cache.get('key');
expect(cache.misses()).to.equal(1);
});
});
describe('keys()', function() {
before(function() {
cache.debug(false);
});
it('should return an empty array given an empty cache', function() {
expect(cache.keys()).to.deep.equal([]);
});
it('should return a single key after adding a single item to the cache', function() {
cache.put('key', 'value');
expect(cache.keys()).to.deep.equal(['key']);
});
it('should return 3 keys after adding three items to the cache', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
expect(cache.keys()).to.deep.equal(['key1', 'key2', 'key3']);
});
it('should not multi-count duplicate items added to the cache', function() {
cache.put('key', 'value1');
expect(cache.keys()).to.deep.equal(['key']);
cache.put('key', 'value2');
expect(cache.keys()).to.deep.equal(['key']);
});
it('should update when a key in the cache expires', function() {
cache.put('key', 'value', 1000);
expect(cache.keys()).to.deep.equal(['key']);
clock.tick(999);
expect(cache.keys()).to.deep.equal(['key']);
clock.tick(1);
expect(cache.keys()).to.deep.equal([]);
});
});
describe('export()', function() {
var START_TIME = 10000;
var BASIC_EXPORT = JSON.stringify({
key: {
value: 'value',
expire: START_TIME + 1000,
},
});
before(function() {
cache.debug(false);
});
beforeEach(function() {
clock.tick(START_TIME);
});
it('should return an empty object given an empty cache', function() {
expect(cache.exportJson()).to.equal(JSON.stringify({}));
});
it('should return a single record after adding a single item to the cache', function() {
cache.put('key', 'value', 1000);
expect(cache.exportJson()).to.equal(BASIC_EXPORT);
});
it('should return multiple records with expiry', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2', 1000);
expect(cache.exportJson()).to.equal(JSON.stringify({
key1: {
value: 'value1',
expire: 'NaN',
},
key2: {
value: 'value2',
expire: START_TIME + 1000,
},
}));
});
it('should update when a key in the cache expires', function() {
cache.put('key', 'value', 1000);
expect(cache.exportJson()).to.equal(BASIC_EXPORT);
clock.tick(999);
expect(cache.exportJson()).to.equal(BASIC_EXPORT);
clock.tick(1);
expect(cache.exportJson()).to.equal(JSON.stringify({}));
});
});
describe('import()', function() {
var START_TIME = 10000;
var BASIC_EXPORT = JSON.stringify({
key: {
value: 'value',
expire: START_TIME + 1000,
},
});
before(function() {
cache.debug(false);
});
beforeEach(function() {
clock.tick(START_TIME);
});
it('should import an empty object into an empty cache', function() {
var exportedJson = cache.exportJson();
cache.clear();
cache.importJson(exportedJson);
expect(cache.exportJson()).to.equal(JSON.stringify({}));
});
it('should import records into an empty cache', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2', 1000);
var exportedJson = cache.exportJson();
cache.clear();
cache.importJson(exportedJson);
expect(cache.exportJson()).to.equal(JSON.stringify({
key1: {
value: 'value1',
expire: 'NaN',
},
key2: {
value: 'value2',
expire: START_TIME + 1000,
},
}));
});
it('should import records into an already-existing cache', function() {
cache.put('key1', 'value1');
cache.put('key2', 'value2', 1000);
var exportedJson = cache.exportJson();
cache.put('key1', 'changed value', 5000);
cache.put('key3', 'value3', 500);
cache.importJson(exportedJson);
expect(cache.exportJson()).to.equal(JSON.stringify({
key1: {
value: 'value1',
expire: 'NaN',
},
key2: {
value: 'value2',
expire: START_TIME + 1000,
},
key3: {
value: 'value3',
expire: START_TIME + 500,
},
}));
});
it('should import records into an already-existing cache and skip duplicates', function() {
cache.debug(true);
cache.put('key1', 'value1');
cache.put('key2', 'value2', 1000);
var exportedJson = cache.exportJson();
cache.clear();
cache.put('key1', 'changed value', 5000);
cache.put('key3', 'value3', 500);
cache.importJson(exportedJson, { skipDuplicates: true });
expect(cache.exportJson()).to.equal(JSON.stringify({
key1: {
value: 'changed value',
expire: START_TIME + 5000,
},
key3: {
value: 'value3',
expire: START_TIME + 500,
},
key2: {
value: 'value2',
expire: START_TIME + 1000,
},
}));
});
it('should import with updated expire times', function() {
cache.put('key1', 'value1', 500);
cache.put('key2', 'value2', 1000);
var exportedJson = cache.exportJson();
var tickAmount = 750;
clock.tick(tickAmount);
cache.importJson(exportedJson);
expect(cache.exportJson()).to.equal(JSON.stringify({
key2: {
value: 'value2',
expire: START_TIME + tickAmount + 250,
},
}));
});
it('should return the new size', function() {
cache.put('key1', 'value1', 500);
var exportedJson = cache.exportJson();
cache.clear();
cache.put('key2', 'value2', 1000);
expect(cache.size()).to.equal(1);
var size = cache.importJson(exportedJson);
expect(size).to.equal(2);
expect(cache.size()).to.equal(2);
});
});
describe('Cache()', function() {
it('should return a new cache instance when called', function() {
var cache1 = new Cache(),
cache2 = new Cache();
cache1.put('key', 'value1');
expect(cache1.keys()).to.deep.equal(['key']);
expect(cache2.keys()).to.deep.equal([]);
cache2.put('key', 'value2');
expect(cache1.get('key')).to.equal('value1');
expect(cache2.get('key')).to.equal('value2');
});
});
});