Browse Source

0.0.1 release

hotfix/0.7.1
Bryan Donovan 12 years ago
parent
commit
204a712103
  1. 3
      .gitignore
  2. 88
      .jshintrc
  3. 0
      History.md
  4. 23
      LICENSE
  5. 25
      Makefile
  6. 146
      README.md
  7. 77
      examples/example.js
  8. 12
      lib/caching.js
  9. 29
      lib/multi_caching.js
  10. 14
      lib/stores/memory.js
  11. 47
      lib/stores/redis.js
  12. 28
      package.json
  13. 147
      readme.html
  14. 197
      test/caching.unit.js
  15. 235
      test/multi_caching.unit.js
  16. 73
      test/run.js
  17. 11
      test/stores/memory.unit.js
  18. 18
      test/stores/redis.unit.js
  19. 75
      test/support.js

3
.gitignore

@ -1 +1,2 @@
node_module node_modules
coverage

88
.jshintrc

@ -0,0 +1,88 @@
{
// Settings
"passfail" : false, // Stop on first error.
"maxerr" : 500, // Maximum errors before stopping.
"multistr" : true,
// Predefined globals whom JSHint will ignore.
"browser" : true, // Standard browser globals e.g. `window`, `document`.
"node" : false,
"rhino" : false,
"couch" : false,
"wsh" : true, // Windows Scripting Host.
"jquery" : true,
"prototypejs" : false,
"mootools" : false,
"dojo" : false,
"predef" : [ // Extra globals.
"__dirname",
"Buffer",
"event",
"exports",
"global",
"module",
"process",
"require",
"daisyjs",
"after",
"afterEach",
"before",
"beforeEach",
"context",
"describe",
"it"
],
// Development.
"debug" : false, // Allow debugger statements e.g. browser breakpoints.
"devel" : true, // Allow development statements e.g. `console.log();`.
// EcmaScript 5.
"es5" : true, // Allow EcmaScript 5 syntax.
"strict" : false, // Require `use strict` pragma in every file.
"globalstrict" : false, // Allow global "use strict" (also enables 'strict').
"asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons).
"laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
"bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.).
"boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
"curly" : true, // Require {} for every new block or scope.
"eqeqeq" : false, // Require triple equals i.e. `===`.
"eqnull" : false, // Tolerate use of `== null`.
"evil" : false, // Tolerate use of `eval`.
"expr" : false, // Tolerate `ExpressionStatement` as Programs.
"forin" : false, // Tolerate `for in` loops without `hasOwnProperty`.
"immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
"latedef" : true, // Prohibit variable use before definition.
"loopfunc" : true, // Allow functions to be defined within loops.
"maxparams" : 4,
"maxdepth" : 5,
"maxcomplexity" : 8,
"maxstatements" : 40,
"noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
"regexp" : false, // Prohibit `.` and `[^...]` in regular expressions.
"regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`.
"scripturl" : true, // Tolerate script-targeted URLs.
"shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
"supernew" : false, // Tolerate `new function () { ... };` and `new Object;`.
"undef" : true, // Require all non-global variables be declared before they are used.
"newcap" : false, // Require capitalization of all constructor functions e.g. `new F()`.
"noempty" : true, // Prohibit use of empty blocks.
"nonew" : false, // Prohibit use of constructors for side-effects.
"nomen" : false, // Prohibit use of initial or trailing underbars in names.
"onevar" : false, // Allow only one `var` statement per function.
"plusplus" : false, // Prohibit use of `++` & `--`.
"sub" : true, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
"trailing" : true, // Prohibit trailing whitespaces. (only works if white is 'true')
"white" : true, // Check against strict whitespace and indentation rules.
"indent" : 4,
"unused" : true
}

0
History.md

23
LICENSE

@ -0,0 +1,23 @@
Copyrights for code authored by MOG Inc. is licensed under the following terms:
MIT License
Copyright (c) 2011 MOG Inc. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

25
Makefile

@ -0,0 +1,25 @@
BASE = .
ISTANBUL = ./node_modules/.bin/istanbul
COVERAGE_OPTS = --lines 99 --statements 95 --branches 90 --functions 95
main: lint test
cover:
$(ISTANBUL) cover test/run.js
check-coverage:
$(ISTANBUL) check-coverage $(COVERAGE_OPTS)
test: cover check-coverage
test-cov: cover check-coverage
open coverage/lcov-report/index.html
lint:
./node_modules/.bin/jshint ./lib --config $(BASE)/.jshintrc && \
./node_modules/.bin/jshint ./test --config $(BASE)/.jshintrc
.PHONY: test

146
README.md

@ -0,0 +1,146 @@
node-cashew
======================
# Flexible NodeJS cache module
A cache module for nodejs that allows easy wrapping of functions in cache,
tiered caches, and a consistent interface.
## Features
* Easy way to wrap any function in cache.
* Tiered caches -- data gets stored in each cache and fetched from the highest
priority cache(s) first.
* Use any cache you want, as long as it has the same API.
* 100% test coverage via [mocha](https://github.com/visionmedia/mocha),
[istanbul](https://github.com/yahoo/istanbul), and [sinon](http://sinonjs.org).
## Installation
npm install node-cashew
## Overview
First, node-cashew features the standard functions you'd expect in most caches:
set(key, val, cb)
get(key, cb)
del(key, cb)
Second, it includes a `wrap` function that lets you wrap any function in cache.
(Note, this was inspired by [node-caching](https://github.com/mape/node-caching).)
Third, node-cashew lets you set up a tiered cache strategy. This may be of
limited use in most cases, but imagine a scenario where you expect tons of
traffic, and don't want to hit Redis for every request. You decide to store
the most commonly-requested data in an in-memory cache (like [node-lru-cache](https://github.com/isaacs/node-lru-cache)),
perhaps with a very short timeout and/or a small data size limit. But you
still want to store the data in Redis for backup, and for the requests that
aren't as common as the ones you want to store in memory. This is something
node-cashew handles easily and transparently.
## Usage Examples
### Single Store
var cashew = require('cashew');
var redis_cache = cashew.caching({store: 'redis', db: 1, ttl: 100/*seconds*/});
var memory_cache = cashew.caching({store: 'memory', max: 100, ttl: 10/*seconds*/});
redis_cache.set('foo', 'bar', function(err) {
if (err) { throw err; }
redis_cache.get('foo', function(err, result) {
console.log(result);
// >> 'bar'
redis_cache.del('foo', function(err) {});
});
});
function get_user(id, cb) {
setTimeout(function () {
console.log("Returning user from slow database.");
cb(null, {id: id, name: 'Bob'});
}, 100);
}
var user_id = 123;
var key = 'user_' + user_id;
redis_cache.wrap(key, function (cb) {
get_user(user_id, cb);
}, function (err, user) {
console.log(user);
// Second time fetches user from redis_cache
redis_cache.wrap(key, function (cb) {
get_user(user_id, cb);
}, function (err, user) {
console.log(user);
});
});
// Outputs:
// Returning user from slow database.
// { id: 123, name: 'Bob' }
// { id: 123, name: 'Bob' }
### Multi-Store
var multi_cache = cashew.multi_caching([memory_cache, redis_cache]);
user_id2 = 456;
key2 = 'user_' + user_id;
// Sets in all caches.
multi_cache.set('foo2', 'bar2', function(err) {
if (err) { throw err; }
// Fetches from highest priority cache that has the key.
multi_cache.get('foo2', function(err, result) {
console.log(result);
// >> 'bar2'
// Delete from all caches
multi_cache.del('foo2');
});
});
multi_cache.wrap(key2, function (cb) {
get_user(user_id2, cb);
}, function (err, user) {
console.log(user);
// Second time fetches user from memory_cache, since it's highest priority.
// If the data expires in the memory cache, the next fetch would pull it from
// the Redis cache, and set the data in memory again.
multi_cache.wrap(key2, function (cb) {
get_user(user_id2, cb);
}, function (err, user) {
console.log(user);
});
});
## Tests
To run tests, first run:
npm install -d
Run the tests and JShint:
make
## Contribute
If you would like to contribute to the project, please fork it and send us a pull request. Please add tests
for any new features or bug fixes. Also run ``make`` before submitting the pull request.
## License
node-cashew is licensed under the MIT license.

77
examples/example.js

@ -0,0 +1,77 @@
var cashew = require('cashew');
var redis_cache = cashew.caching({store: 'redis', db: 1, ttl: 100/*seconds*/});
var memory_cache = cashew.caching({store: 'memory', max: 100, ttl: 10/*seconds*/});
redis_cache.set('foo', 'bar', function(err) {
if (err) { throw err; }
redis_cache.get('foo', function(err, result) {
console.log(result);
// >> 'bar'
redis_cache.del('foo', function(err) {});
});
});
function get_user(id, cb) {
setTimeout(function () {
console.log("Returning user from slow database.");
cb(null, {id: id, name: 'Bob'});
}, 100);
}
var user_id = 123;
var key = 'user_' + user_id;
redis_cache.wrap(key, function (cb) {
get_user(user_id, cb);
}, function (err, user) {
console.log(user);
// Second time fetches user from redis_cache
redis_cache.wrap(key, function (cb) {
get_user(user_id, cb);
}, function (err, user) {
console.log(user);
});
});
// Outputs:
// Returning user from slow database.
// { id: 123, name: 'Bob' }
// { id: 123, name: 'Bob' }
var multi_cache = cashew.multi_caching([memory_cache, redis_cache]);
user_id2 = 456;
key2 = 'user_' + user_id;
multi_cache.wrap(key2, function (cb) {
get_user(user_id2, cb);
}, function (err, user) {
console.log(user);
// Second time fetches user from memory_cache, since it's highest priority.
// If the data expires in the memory cache, the next fetch would pull it from
// the Redis cache, and set the data in memory again.
multi_cache.wrap(key2, function (cb) {
get_user(user_id2, cb);
}, function (err, user) {
console.log(user);
});
// Sets in all caches.
multi_cache.set('foo2', 'bar2', function(err) {
if (err) { throw err; }
// Fetches from highest priority cache that has the key.
multi_cache.get('foo2', function(err, result) {
console.log(result);
// >> 'bar2'
// Delete from all caches
multi_cache.del('foo2', function(err) {
process.exit();
});
});
});
});

12
lib/caching.js

@ -1,4 +1,4 @@
var caching = function(args) { var caching = function (args) {
args = args || {}; args = args || {};
var self = {}; var self = {};
if (typeof args.store === 'object') { if (typeof args.store === 'object') {
@ -6,7 +6,7 @@ var caching = function(args) {
} else if (typeof args.store === 'string' && args.store.match(/\//)) { } else if (typeof args.store === 'string' && args.store.match(/\//)) {
self.store = require(args.store).create(args); self.store = require(args.store).create(args);
} else { } else {
var store_name = args.store || 'redis'; var store_name = args.store || 'memory';
self.store = require('./stores/' + store_name).create(args); self.store = require('./stores/' + store_name).create(args);
} }
@ -24,16 +24,16 @@ var caching = function(args) {
* console.log(user); * console.log(user);
* }); * });
*/ */
self.wrap = function(key, work, cb) { self.wrap = function (key, work, cb) {
self.store.get(key, function(err, result) { self.store.get(key, function (err, result) {
if (err) { return cb(err); } if (err) { return cb(err); }
if (result) { if (result) {
return cb(null, result); return cb(null, result);
} }
work(function() { work(function () {
var work_args = Array.prototype.slice.call(arguments, 0); var work_args = Array.prototype.slice.call(arguments, 0);
self.store.set(key, work_args[1], function(err) { self.store.set(key, work_args[1], function (err) {
if (err) { return cb(err); } if (err) { return cb(err); }
cb.apply(null, work_args); cb.apply(null, work_args);
}); });

29
lib/multi_caching.js

@ -3,14 +3,14 @@ var async = require('async');
/** /**
* Module that lets you specify a hiearchy of caches. * Module that lets you specify a hiearchy of caches.
*/ */
var multi_caching = function(caches) { var multi_caching = function (caches) {
var self = {}; var self = {};
if (!Array.isArray(caches)) { throw new Error('multi_caching requires an array of caches'); } if (!Array.isArray(caches)) { throw new Error('multi_caching requires an array of caches'); }
function get_from_highest_priority_cache(key, cb) { function get_from_highest_priority_cache(key, cb) {
var i = 0; var i = 0;
async.forEachSeries(caches, function(cache, async_cb) { async.forEachSeries(caches, function (cache, async_cb) {
cache.store.get(key, function(err, result) { cache.store.get(key, function (err, result) {
if (err) { return cb(err); } if (err) { return cb(err); }
if (result) { if (result) {
// break out of async loop. // break out of async loop.
@ -24,10 +24,10 @@ var multi_caching = function(caches) {
} }
function set_in_multiple_caches(caches, key, value, cb) { function set_in_multiple_caches(caches, key, value, cb) {
async.forEach(caches, function(cache, async_cb) { async.forEach(caches, function (cache, async_cb) {
cache.store.set(key, value, async_cb); cache.store.set(key, value, async_cb);
}, cb); }, cb);
}; }
/** /**
* Wraps a function in one or more caches. * Wraps a function in one or more caches.
@ -39,19 +39,20 @@ var multi_caching = function(caches) {
* If a key doesn't exist in a higher-priority cache but exists in a lower-priority * If a key doesn't exist in a higher-priority cache but exists in a lower-priority
* cache, it gets set in all higher-priority caches. * cache, it gets set in all higher-priority caches.
*/ */
self.wrap = function(key, work, cb) { self.wrap = function (key, work, cb) {
get_from_highest_priority_cache(key, function(err, result, index) { get_from_highest_priority_cache(key, function (err, result, index) {
if (err) { return cb(err); } if (err) { return cb(err); }
if (result) { if (result) {
var caches_to_set = caches.slice(0, index); var caches_to_set = caches.slice(0, index);
set_in_multiple_caches(caches_to_set, key, result, function(err) { set_in_multiple_caches(caches_to_set, key, result, function (err) {
return cb(err, result); return cb(err, result);
}); });
} else { } else {
work(function() { work(function () {
var work_args = Array.prototype.slice.call(arguments, 0); var work_args = Array.prototype.slice.call(arguments, 0);
set_in_multiple_caches(caches, key, work_args[1], function(err) { set_in_multiple_caches(caches, key, work_args[1], function (err) {
if (err) { return cb(err); }
cb.apply(null, work_args); cb.apply(null, work_args);
}); });
}); });
@ -59,16 +60,16 @@ var multi_caching = function(caches) {
}); });
}; };
self.set = function(key, value, cb) { self.set = function (key, value, cb) {
set_in_multiple_caches(caches, key, value, cb); set_in_multiple_caches(caches, key, value, cb);
}; };
self.get = function(key, cb) { self.get = function (key, cb) {
get_from_highest_priority_cache(key, cb); get_from_highest_priority_cache(key, cb);
}; };
self.del = function(key, cb) { self.del = function (key, cb) {
async.forEach(caches, function(cache, async_cb) { async.forEach(caches, function (cache, async_cb) {
cache.store.del(key, async_cb); cache.store.del(key, async_cb);
}, cb); }, cb);
}; };

14
lib/stores/memory.js

@ -1,9 +1,9 @@
var Lru = require("lru-cache") var Lru = require("lru-cache");
var memory_store = function(args) { var memory_store = function (args) {
args = args || {}; args = args || {};
var db = args.db || 'cache';
var self = {}; var self = {};
self.name = 'memory';
var ttl = args.ttl; var ttl = args.ttl;
var lru_opts = { var lru_opts = {
max: args.max || 500, max: args.max || 500,
@ -12,16 +12,16 @@ var memory_store = function(args) {
var lru_cache = new Lru(lru_opts); var lru_cache = new Lru(lru_opts);
self.set = function(key, value, cb) { self.set = function (key, value, cb) {
lru_cache.set(key, value); lru_cache.set(key, value);
cb(null); cb(null);
}; };
self.get = function(key, cb) { self.get = function (key, cb) {
cb(null, lru_cache.get(key)); cb(null, lru_cache.get(key));
}; };
self.del = function(key, cb) { self.del = function (key, cb) {
lru_cache.del(key); lru_cache.del(key);
cb(null); cb(null);
}; };
@ -30,7 +30,7 @@ var memory_store = function(args) {
}; };
var methods = { var methods = {
create: function(args) { create: function (args) {
return memory_store(args); return memory_store(args);
} }
}; };

47
lib/stores/redis.js

@ -1,49 +1,20 @@
/*
var redis_store = function(args) {
args = args || {};
var db = args.db || 'cache';
var self = {};
var ttl = args.ttl;
var client = djs.backends.redis.client({db: djs.settings.redis.dbs[db]});
self.set = function(key, value, cb) {
var val = JSON.stringify(value);
if (ttl) {
client.command('setex', {key: key, ttl: ttl, value: val}, cb);
} else {
client.command('set', {key: key, value: val}, cb);
}
};
self.get = function(key, cb) {
client.command('get', {key: key}, function(err, result) {
if (err) { return cb(err); }
if (result === undefined) { return cb(null, null); }
return cb(null, JSON.parse(result));
});
};
self.del = function(key, cb) {
client.command('del', {key: key}, cb);
};
return self;
};
*/
function redis_store(args) { function redis_store(args) {
args = args || {}; args = args || {};
var self = {}; var self = {};
var ttl = args.ttl; var ttl = args.ttl;
self.name = 'redis';
self.client = require('redis').createClient(args.port, args.host, args); self.client = require('redis').createClient(args.port, args.host, args);
if (args.db) {
self.client.select(args.db);
}
self.get = function(key, cb) { self.get = function (key, cb) {
self.client.get(key, function(err, result) { self.client.get(key, function (err, result) {
cb(err, JSON.parse(result)); cb(err, JSON.parse(result));
}); });
}; };
self.set = function(key, value, cb) { self.set = function (key, value, cb) {
if (ttl) { if (ttl) {
self.client.setex(key, ttl, JSON.stringify(value), cb); self.client.setex(key, ttl, JSON.stringify(value), cb);
} else { } else {
@ -51,7 +22,7 @@ function redis_store(args) {
} }
}; };
self.del = function(key, cb) { self.del = function (key, cb) {
self.client.del(key, cb); self.client.del(key, cb);
}; };
@ -60,7 +31,7 @@ function redis_store(args) {
var methods = { var methods = {
create: function(args) { create: function (args) {
return redis_store(args); return redis_store(args);
} }
}; };

28
package.json

@ -1,7 +1,7 @@
{ {
"name": "cashew", "name": "cache-manager",
"version": "0.0.1", "version": "0.0.1",
"description": "Cache module for Node.JS", "description": "Cache module for Node.js",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "mocha" "test": "mocha"
@ -13,21 +13,23 @@
"keywords": [ "keywords": [
"cache", "cache",
"redis", "redis",
"lru-cache" "lru-cache",
"memory cache",
"multiple cache"
], ],
"author": "Bryan Donovan", "author": "Bryan Donovan",
"license": "BSD", "license": "MIT",
"dependencies": { "dependencies": {
"async": "0.1.22", "async": ">=0.1.22",
"hiredis": "0.1.14", "hiredis": ">=0.1.14",
"lru-cache": "2.3.0", "lru-cache": ">=2.3.0",
"redis": "0.6.7" "optimist": ">=0.3.5",
"redis": ">=0.6.7"
}, },
"devDependencies": { "devDependencies": {
"Faker" : "0.5.6", "istanbul": ">=0.1.29",
"istanbul": "0.1.29", "jshint": ">=1.0.1",
"jshint": "1.0.0", "mocha": ">=1.8.1",
"mocha": "1.8.1", "sinon": ">=1.5.2"
"sinon": "1.5.2"
} }
} }

147
readme.html

@ -0,0 +1,147 @@
<h1>node-cashew</h1>
<h1>Flexible NodeJS cache module</h1>
<p>A cache module for nodejs that allows easy wrapping of functions in cache,
tiered caches, and a consistent interface.</p>
<h2>Features</h2>
<ul>
<li>Easy way to wrap any function in cache.</li>
<li>Tiered caches -- data gets stored in each cache and fetched from the highest
priority cache(s) first.</li>
<li>Use any cache you want, as long as it has the same API.</li>
<li>100% test coverage via <a href="https://github.com/visionmedia/mocha">mocha</a>,
<a href="https://github.com/yahoo/istanbul">istanbul</a>, and <a href="http://sinonjs.org">sinon</a>.</li>
</ul>
<h2>Installation</h2>
<pre><code>npm install node-cashew
</code></pre>
<h2>Overview</h2>
<p>First, node-cashew features the standard functions you'd expect in most caches:</p>
<pre><code>set(key, val, cb)
get(key, cb)
del(key, cb)
</code></pre>
<p>Second, it includes a <code>wrap</code> function that lets you wrap any function in cache.
(Note, this was inspired by <a href="https://github.com/mape/node-caching">node-caching</a>.)</p>
<p>Third, node-cashew lets you set up a tiered cache strategy. This may be of
limited use in most cases, but imagine a scenario where you expect tons of
traffic, and don't want to hit Redis for every request. You decide to store
the most commonly-requested data in an in-memory cache (like <a href="https://github.com/isaacs/node-lru-cache">node-lru-cache</a>),
perhaps with a very short timeout and/or a small data size limit. But you
still want to store the data in Redis for backup, and for the requests that
aren't as common as the ones you want to store in memory. This is something
node-cashew handles easily and transparently.</p>
<h2>Usage Examples</h2>
<h3>Single Store</h3>
<pre><code> var cashew = require('cashew');
var redis_cache = cashew.caching({store: 'redis', db: 1, ttl: 100/*seconds*/});
var memory_cache = cashew.caching({store: 'memory', max: 100, ttl: 10/*seconds*/});
redis_cache.set('foo', 'bar', function(err) {
if (err) { throw err; }
redis_cache.get('foo', function(err, result) {
console.log(result);
// &gt;&gt; 'bar'
redis_cache.del('foo', function(err) {});
});
});
function get_user(id, cb) {
setTimeout(function () {
console.log("Returning user from slow database.");
cb(null, {id: id, name: 'Bob'});
}, 100);
}
var user_id = 123;
var key = 'user_' + user_id;
redis_cache.wrap(key, function (cb) {
get_user(user_id, cb);
}, function (err, user) {
console.log(user);
// Second time fetches user from redis_cache
redis_cache.wrap(key, function (cb) {
get_user(user_id, cb);
}, function (err, user) {
console.log(user);
});
});
// Outputs:
// Returning user from slow database.
// { id: 123, name: 'Bob' }
// { id: 123, name: 'Bob' }
</code></pre>
<h3>Multi-Store</h3>
<pre><code> var multi_cache = cashew.multi_caching([memory_cache, redis_cache]);
user_id2 = 456;
key2 = 'user_' + user_id;
// Sets in all caches.
multi_cache.set('foo2', 'bar2', function(err) {
if (err) { throw err; }
// Fetches from highest priority cache that has the key.
multi_cache.get('foo2', function(err, result) {
console.log(result);
// &gt;&gt; 'bar2'
// Delete from all caches
multi_cache.del('foo2');
});
});
multi_cache.wrap(key2, function (cb) {
get_user(user_id2, cb);
}, function (err, user) {
console.log(user);
// Second time fetches user from memory_cache, since it's highest priority.
// If the data expires in the memory cache, the next fetch would pull it from
// the Redis cache, and set the data in memory again.
multi_cache.wrap(key2, function (cb) {
get_user(user_id2, cb);
}, function (err, user) {
console.log(user);
});
});
</code></pre>
<h2>Tests</h2>
<p>To run tests, first run:</p>
<pre><code>npm install -d
</code></pre>
<p>Run the tests and JShint:</p>
<pre><code>make
</code></pre>
<h2>Contribute</h2>
<p>If you would like to contribute to the project, please fork it and send us a pull request. Please add tests
for any new features or bug fixes. Also run <code>make</code> before submitting the pull request.</p>
<h2>License</h2>
<p>node-easy-mysql is licensed under the MIT license.</p>

197
test/caching.unit.js

@ -1,7 +1,6 @@
var assert = require('assert'); var assert = require('assert');
var sinon = require('sinon'); var sinon = require('sinon');
var redis = require('redis'); var redis = require('redis');
var Lru = require("lru-cache")
var support = require('./support'); var support = require('./support');
var check_err = support.check_err; var check_err = support.check_err;
var caching = require('../index').caching; var caching = require('../index').caching;
@ -11,25 +10,25 @@ function get_widget(name, cb) {
cb(null, {name: name}); cb(null, {name: name});
} }
describe("caching", function() { describe("caching", function () {
var cache; var cache;
var key; var key;
var ttl; var ttl;
var name; var name;
var value; var value;
describe("get() and set()", function() { describe("get() and set()", function () {
context("using redis store", function() { context("using redis store", function () {
beforeEach(function() { beforeEach(function () {
cache = caching({store: 'redis'}); cache = caching({store: 'redis'});
key = support.random.string(20); key = support.random.string(20);
value = support.random.string(); value = support.random.string();
}); });
it("lets us set and get data in cache", function(done) { it("lets us set and get data in cache", function (done) {
cache.set(key, value, function(err, result) { cache.set(key, value, function (err) {
check_err(err); check_err(err);
cache.get(key, function(err, result) { cache.get(key, function (err, result) {
assert.equal(result, value); assert.equal(result, value);
done(); done();
}); });
@ -37,17 +36,17 @@ describe("caching", function() {
}); });
}); });
context("using memory store", function() { context("using memory store", function () {
beforeEach(function() { beforeEach(function () {
cache = caching({store: 'memory'}); cache = caching({store: 'memory'});
key = support.random.string(20); key = support.random.string(20);
value = support.random.string(); value = support.random.string();
}); });
it("lets us set and get data in cache", function(done) { it("lets us set and get data in cache", function (done) {
cache.set(key, value, function(err, result) { cache.set(key, value, function (err) {
check_err(err); check_err(err);
cache.get(key, function(err, result) { cache.get(key, function (err, result) {
assert.equal(result, value); assert.equal(result, value);
done(); done();
}); });
@ -56,27 +55,27 @@ describe("caching", function() {
}); });
}); });
describe("del()", function() { describe("del()", function () {
['redis', 'memory'].forEach(function(store) { ['redis', 'memory'].forEach(function (store) {
context("using " + store + " store", function() { context("using " + store + " store", function () {
beforeEach(function(done) { beforeEach(function (done) {
cache = caching({store: store}); cache = caching({store: store});
key = support.random.string(20); key = support.random.string(20);
value = support.random.string(); value = support.random.string();
cache.set(key, value, function(err, result) { cache.set(key, value, function (err) {
check_err(err); check_err(err);
done(); done();
}); });
}); });
it("deletes data from cache", function(done) { it("deletes data from cache", function (done) {
cache.get(key, function(err, result) { cache.get(key, function (err, result) {
assert.equal(result, value); assert.equal(result, value);
cache.del(key, function(err, result) { cache.del(key, function (err) {
check_err(err); check_err(err);
cache.get(key, function(err, result) { cache.get(key, function (err, result) {
assert.ok(!result); assert.ok(!result);
done(); done();
}); });
@ -87,42 +86,43 @@ describe("caching", function() {
}); });
}); });
describe("wrap()", function() { describe("wrap()", function () {
context("using redis store", function() { context("using redis store", function () {
var redis_client; var redis_client;
before(function() { before(function () {
redis_client = redis.createClient(); redis_client = redis.createClient();
sinon.stub(redis, 'createClient').returns(redis_client); sinon.stub(redis, 'createClient').returns(redis_client);
}); });
beforeEach(function() { beforeEach(function () {
cache = caching({store: 'redis'}); cache = caching({store: 'redis'});
key = support.random.string(20); key = support.random.string(20);
name = support.random.string(); name = support.random.string();
}); });
after(function() { after(function () {
redis.createClient.restore(); redis.createClient.restore();
}); });
it("calls back with the result of the wrapped function", function(done) { it("calls back with the result of the wrapped function", function (done) {
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
done(); done();
}); });
}); });
it("caches the result of the function in redis", function(done) { it("caches the result of the function in redis", function (done) {
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.ok(widget);
redis_client.get(key, function(err, result) { redis_client.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(JSON.parse(result), {name: name}); assert.deepEqual(JSON.parse(result), {name: name});
@ -131,20 +131,22 @@ describe("caching", function() {
}); });
}); });
it("retrieves data from redis when available", function(done) { it("retrieves data from redis when available", function (done) {
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.ok(widget);
redis_client.get(key, function(err, result) { redis_client.get(key, function (err, result) {
check_err(err); check_err(err);
assert.ok(result);
sinon.spy(redis_client, 'get'); sinon.spy(redis_client, 'get');
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
assert.ok(redis_client.get.calledWith(key)); assert.ok(redis_client.get.calledWith(key));
@ -155,19 +157,20 @@ describe("caching", function() {
}); });
}); });
context("when using ttl", function() { context("when using ttl", function () {
beforeEach(function() { beforeEach(function () {
ttl = 50; ttl = 50;
cache = caching({store: 'redis', ttl: ttl}); cache = caching({store: 'redis', ttl: ttl});
}); });
it("expires cached result after ttl seconds", function(done) { it("expires cached result after ttl seconds", function (done) {
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.ok(widget);
redis_client.ttl(key, function(err, result) { redis_client.ttl(key, function (err, result) {
check_err(err); check_err(err);
support.assert_within(result, ttl, 2); support.assert_within(result, ttl, 2);
done(); done();
@ -178,10 +181,10 @@ describe("caching", function() {
}); });
describe("using memory (lru-cache) store", function() { describe("using memory (lru-cache) store", function () {
var memory_store_stub; var memory_store_stub;
beforeEach(function() { beforeEach(function () {
ttl = 0.1; ttl = 0.1;
memory_store_stub = memory_store.create({ttl: ttl}); memory_store_stub = memory_store.create({ttl: ttl});
@ -192,38 +195,40 @@ describe("caching", function() {
name = support.random.string(); name = support.random.string();
}); });
afterEach(function() { afterEach(function () {
memory_store.create.restore(); memory_store.create.restore();
}); });
it("calls back with the result of a function", function(done) { it("calls back with the result of a function", function (done) {
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
done(); done();
}); });
}); });
it("retrieves data from memory when available", function(done) { it("retrieves data from memory when available", function (done) {
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.ok(widget);
memory_store_stub.get(key, function(err, result) { memory_store_stub.get(key, function (err, result) {
check_err(err); check_err(err);
assert.ok(result);
sinon.spy(memory_store_stub, 'get'); sinon.spy(memory_store_stub, 'get');
var func_called = false; var func_called = false;
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, function(err, result) { get_widget(name, function (err, result) {
func_called = true; func_called = true;
cb(err, result); cb(err, result);
}); });
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
assert.ok(memory_store_stub.get.calledWith(key)); assert.ok(memory_store_stub.get.calledWith(key));
@ -235,25 +240,26 @@ describe("caching", function() {
}); });
}); });
it("expires cached result after ttl seconds", function(done) { it("expires cached result after ttl seconds", function (done) {
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name});
memory_store_stub.get(key, function(err, result) { memory_store_stub.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.ok(result);
var func_called = false; var func_called = false;
setTimeout(function () { setTimeout(function () {
cache.wrap(key, function(cb) { cache.wrap(key, function (cb) {
get_widget(name, function(err, result) { get_widget(name, function (err, result) {
func_called = true; func_called = true;
cb(err, result); cb(err, result);
}); });
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.ok(func_called); assert.ok(func_called);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
@ -263,28 +269,71 @@ describe("caching", function() {
}); });
}); });
}); });
context("when store.get() calls back with an error", function () {
it("bubbles up that error", function (done) {
var fake_error = new Error(support.random.string());
sinon.stub(memory_store_stub, 'get', function (key, cb) {
cb(fake_error);
});
cache.wrap(key, function (cb) {
get_widget(name, cb);
}, function (err) {
assert.equal(err, fake_error);
memory_store_stub.get.restore();
done();
});
});
});
context("when store.set() calls back with an error", function () {
it("bubbles up that error", function (done) {
var fake_error = new Error(support.random.string());
sinon.stub(memory_store_stub, 'set', function (key, val, cb) {
cb(fake_error);
});
cache.wrap(key, function (cb) {
get_widget(name, cb);
}, function (err) {
assert.equal(err, fake_error);
memory_store_stub.set.restore();
done();
});
});
});
});
});
describe("instantiating with no store passed in", function () {
it("defaults to 'memory' store", function () {
var cache = caching();
assert.equal(cache.store.name, 'memory');
}); });
}); });
describe("instantiating with custom store", function() { describe("instantiating with custom store", function () {
it("allows us to pass in our own store object", function(done) { it("allows us to pass in our own store object", function (done) {
var store = memory_store.create({ttl: ttl}); var store = memory_store.create({ttl: ttl});
cache = caching({store: store}); cache = caching({store: store});
cache.set(key, value, function(err, result) { cache.set(key, value, function (err) {
check_err(err); check_err(err);
cache.get(key, function(err, result) { cache.get(key, function (err, result) {
assert.equal(result, value); assert.equal(result, value);
done(); done();
}); });
}); });
}); });
it("allows us to pass in a path to our own store", function(done) { it("allows us to pass in a path to our own store", function (done) {
var store_path = '../lib/stores/memory'; var store_path = '../lib/stores/memory';
cache = caching({store: store_path}); cache = caching({store: store_path});
cache.set(key, value, function(err, result) { cache.set(key, value, function (err) {
check_err(err); check_err(err);
cache.get(key, function(err, result) { cache.get(key, function (err, result) {
assert.equal(result, value); assert.equal(result, value);
done(); done();
}); });

235
test/multi_caching.unit.js

@ -1,5 +1,5 @@
var assert = require('assert'); var assert = require('assert');
var Lru = require("lru-cache") var sinon = require('sinon');
var support = require('./support'); var support = require('./support');
var check_err = support.check_err; var check_err = support.check_err;
var caching = require('../index').caching; var caching = require('../index').caching;
@ -10,7 +10,7 @@ function get_widget(name, cb) {
cb(null, {name: name}); cb(null, {name: name});
} }
describe("multi_caching", function() { describe("multi_caching", function () {
var redis_cache; var redis_cache;
var memory_cache; var memory_cache;
var memory_cache2; var memory_cache2;
@ -20,7 +20,7 @@ describe("multi_caching", function() {
var redis_ttl; var redis_ttl;
var name; var name;
beforeEach(function() { beforeEach(function () {
memory_ttl = 0.1; memory_ttl = 0.1;
redis_ttl = 1; redis_ttl = 1;
@ -32,27 +32,27 @@ describe("multi_caching", function() {
name = support.random.string(); name = support.random.string();
}); });
describe("get(), set(), del()", function() { describe("get(), set(), del()", function () {
var value; var value;
beforeEach(function() { beforeEach(function () {
multi_cache = multi_caching([memory_cache, redis_cache, memory_cache2]); multi_cache = multi_caching([memory_cache, redis_cache, memory_cache2]);
key = support.random.string(20); key = support.random.string(20);
value = support.random.string(); value = support.random.string();
}); });
describe("set()", function() { describe("set()", function () {
it("lets us set data in all caches", function(done) { it("lets us set data in all caches", function (done) {
multi_cache.set(key, value, function(err, result) { multi_cache.set(key, value, function (err) {
check_err(err); check_err(err);
memory_cache.get(key, function(err, result) { memory_cache.get(key, function (err, result) {
assert.equal(result, value); assert.equal(result, value);
redis_cache.get(key, function(err, result) { redis_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.equal(result, value); assert.equal(result, value);
memory_cache2.get(key, function(err, result) { memory_cache2.get(key, function (err, result) {
check_err(err); check_err(err);
assert.equal(result, value); assert.equal(result, value);
done(); done();
@ -63,12 +63,12 @@ describe("multi_caching", function() {
}); });
}); });
describe("get()", function() { describe("get()", function () {
it("gets data from first cache that has it", function(done) { it("gets data from first cache that has it", function (done) {
redis_cache.set(key, value, function(err) { redis_cache.set(key, value, function (err) {
check_err(err); check_err(err);
multi_cache.get(key, function(err, result) { multi_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.equal(result, value); assert.equal(result, value);
done(); done();
@ -77,22 +77,22 @@ describe("multi_caching", function() {
}); });
}); });
describe("del()", function() { describe("del()", function () {
it("lets us delete data in all caches", function(done) { it("lets us delete data in all caches", function (done) {
multi_cache.set(key, value, function(err, result) { multi_cache.set(key, value, function (err) {
check_err(err); check_err(err);
multi_cache.del(key, function(err, result) { multi_cache.del(key, function (err) {
check_err(err); check_err(err);
memory_cache.get(key, function(err, result) { memory_cache.get(key, function (err, result) {
assert.ok(!result); assert.ok(!result);
redis_cache.get(key, function(err, result) { redis_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.ok(!result); assert.ok(!result);
memory_cache2.get(key, function(err, result) { memory_cache2.get(key, function (err, result) {
check_err(err); check_err(err);
assert.ok(!result); assert.ok(!result);
done(); done();
@ -105,16 +105,16 @@ describe("multi_caching", function() {
}); });
}); });
describe("wrap()", function() { describe("wrap()", function () {
describe("using a single cache store", function() { describe("using a single cache store", function () {
beforeEach(function() { beforeEach(function () {
multi_cache = multi_caching([redis_cache]); multi_cache = multi_caching([redis_cache]);
}); });
it("calls back with the result of a function", function(done) { it("calls back with the result of a function", function (done) {
multi_cache.wrap(key, function(cb) { multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
done(); done();
@ -122,33 +122,33 @@ describe("multi_caching", function() {
}); });
}); });
describe("using two cache stores", function() { describe("using two cache stores", function () {
beforeEach(function() { beforeEach(function () {
multi_cache = multi_caching([memory_cache, redis_cache]); multi_cache = multi_caching([memory_cache, redis_cache]);
}); });
it("calls back with the result of a function", function(done) { it("calls back with the result of a function", function (done) {
multi_cache.wrap(key, function(cb) { multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
done(); done();
}); });
}); });
it("sets value in all caches", function(done) { it("sets value in all caches", function (done) {
multi_cache.wrap(key, function(cb) { multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
memory_cache.get(key, function(err, result) { memory_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(result, {name: name}); assert.deepEqual(result, {name: name});
redis_cache.get(key, function(err, result) { redis_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(result, {name: name}); assert.deepEqual(result, {name: name});
done(); done();
@ -157,16 +157,18 @@ describe("multi_caching", function() {
}); });
}); });
context("when value exists in first store but not second", function() { context("when value exists in first store but not second", function () {
it("returns value from first store, does not set it in second", function(done) { it("returns value from first store, does not set it in second", function (done) {
memory_cache.set(key, {name: name}, function(err) { memory_cache.set(key, {name: name}, function (err) {
multi_cache.wrap(key, function(cb) { check_err(err);
multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
redis_cache.get(key, function(err, result) { redis_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.equal(result, null); assert.equal(result, null);
done(); done();
@ -176,16 +178,18 @@ describe("multi_caching", function() {
}); });
}); });
context("when value exists in second store but not first", function() { context("when value exists in second store but not first", function () {
it("returns value from second store, sets it in first store", function(done) { it("returns value from second store, sets it in first store", function (done) {
redis_cache.set(key, {name: name}, function(err) { redis_cache.set(key, {name: name}, function (err) {
multi_cache.wrap(key, function(cb) { check_err(err);
multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
memory_cache.get(key, function(err, result) { memory_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(result, {name: name}); assert.deepEqual(result, {name: name});
done(); done();
@ -196,37 +200,37 @@ describe("multi_caching", function() {
}); });
}); });
describe("using three cache stores", function() { describe("using three cache stores", function () {
beforeEach(function() { beforeEach(function () {
multi_cache = multi_caching([memory_cache, redis_cache, memory_cache2]); multi_cache = multi_caching([memory_cache, redis_cache, memory_cache2]);
}); });
it("calls back with the result of a function", function(done) { it("calls back with the result of a function", function (done) {
multi_cache.wrap(key, function(cb) { multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
done(); done();
}); });
}); });
it("sets value in all caches", function(done) { it("sets value in all caches", function (done) {
multi_cache.wrap(key, function(cb) { multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
memory_cache.get(key, function(err, result) { memory_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(result, {name: name}); assert.deepEqual(result, {name: name});
redis_cache.get(key, function(err, result) { redis_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(result, {name: name}); assert.deepEqual(result, {name: name});
memory_cache2.get(key, function(err, result) { memory_cache2.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(result, {name: name}); assert.deepEqual(result, {name: name});
done(); done();
@ -236,20 +240,22 @@ describe("multi_caching", function() {
}); });
}); });
context("when value exists in first store only", function() { context("when value exists in first store only", function () {
it("returns value from first store, does not set it in second or third", function(done) { it("returns value from first store, does not set it in second or third", function (done) {
memory_cache.set(key, {name: name}, function(err) { memory_cache.set(key, {name: name}, function (err) {
multi_cache.wrap(key, function(cb) { check_err(err);
multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
redis_cache.get(key, function(err, result) { redis_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.equal(result, null); assert.equal(result, null);
memory_cache2.get(key, function(err, result) { memory_cache2.get(key, function (err, result) {
check_err(err); check_err(err);
assert.equal(result, null); assert.equal(result, null);
done(); done();
@ -260,20 +266,22 @@ describe("multi_caching", function() {
}); });
}); });
context("when value exists in second store only", function() { context("when value exists in second store only", function () {
it("returns value from second store, sets it in first store, does not set third store", function(done) { it("returns value from second store, sets it in first store, does not set third store", function (done) {
redis_cache.set(key, {name: name}, function(err) { redis_cache.set(key, {name: name}, function (err) {
multi_cache.wrap(key, function(cb) { check_err(err);
multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
memory_cache.get(key, function(err, result) { memory_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(result, {name: name}); assert.deepEqual(result, {name: name});
memory_cache2.get(key, function(err, result) { memory_cache2.get(key, function (err, result) {
check_err(err); check_err(err);
assert.equal(result, null); assert.equal(result, null);
done(); done();
@ -284,20 +292,22 @@ describe("multi_caching", function() {
}); });
}); });
context("when value exists in third store only", function() { context("when value exists in third store only", function () {
it("returns value from third store, sets it in first and second stores", function(done) { it("returns value from third store, sets it in first and second stores", function (done) {
memory_cache2.set(key, {name: name}, function(err) { memory_cache2.set(key, {name: name}, function (err) {
multi_cache.wrap(key, function(cb) { check_err(err);
multi_cache.wrap(key, function (cb) {
get_widget(name, cb); get_widget(name, cb);
}, function(err, widget) { }, function (err, widget) {
check_err(err); check_err(err);
assert.deepEqual(widget, {name: name}); assert.deepEqual(widget, {name: name});
redis_cache.get(key, function(err, result) { redis_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(result, {name: name}); assert.deepEqual(result, {name: name});
memory_cache.get(key, function(err, result) { memory_cache.get(key, function (err, result) {
check_err(err); check_err(err);
assert.deepEqual(result, {name: name}); assert.deepEqual(result, {name: name});
@ -309,5 +319,66 @@ describe("multi_caching", function() {
}); });
}); });
}); });
context("error handling", function () {
var memory_store_stub;
var ttl;
beforeEach(function () {
ttl = 0.1;
memory_store_stub = memory_store.create({ttl: ttl});
sinon.stub(memory_store, 'create').returns(memory_store_stub);
memory_cache = caching({store: 'memory', ttl: ttl});
multi_cache = multi_caching([memory_cache]);
});
afterEach(function () {
memory_store.create.restore();
});
context("when store.get() calls back with an error", function () {
it("bubbles up that error", function (done) {
var fake_error = new Error(support.random.string());
sinon.stub(memory_store_stub, 'get', function (key, cb) {
cb(fake_error);
});
multi_cache.wrap(key, function (cb) {
get_widget(name, cb);
}, function (err) {
assert.equal(err, fake_error);
memory_store_stub.get.restore();
done();
});
});
});
context("when store.set() calls back with an error", function () {
it("bubbles up that error", function (done) {
var fake_error = new Error(support.random.string());
sinon.stub(memory_store_stub, 'set', function (key, val, cb) {
cb(fake_error);
});
multi_cache.wrap(key, function (cb) {
get_widget(name, cb);
}, function (err) {
assert.equal(err, fake_error);
memory_store_stub.set.restore();
done();
});
});
});
});
});
context("when instantiated non-Array 'caches' arg", function () {
it("throws an error", function () {
assert.throws(function () {
multi_caching({foo: 'bar'});
}, /multi_caching requires an array/);
});
}); });
}); });

73
test/run.js

@ -0,0 +1,73 @@
#!/usr/bin/env node
process.env.NODE_ENV = 'test';
require('../index');
var Mocha = require('mocha');
var optimist = require('optimist');
var walk_dir = require('./support').walk_dir;
var argv = optimist
.usage("Usage: $0 -t [types] --reporter [reporter] --timeout [timeout]")
.default({types: 'unit,functional', reporter: 'spec', timeout: 6000})
.describe('types', 'The types of tests to run, separated by commas. E.g., unit,functional,acceptance')
.describe('reporter', 'The mocha test reporter to use.')
.describe('timeout', 'The mocha timeout to use per test (ms).')
.boolean('help')
.alias('types', 'T')
.alias('timeout', 't')
.alias('reporter', 'R')
.alias('help', 'h')
.argv;
var mocha = new Mocha({timeout: argv.timeout, reporter: argv.reporter, ui: 'bdd'});
var valid_test_types = ['unit', 'functional', 'acceptance', 'integration'];
var requested_types = argv.types.split(',');
var types_to_use = [];
valid_test_types.forEach(function (valid_test_type) {
if (requested_types.indexOf(valid_test_type) !== -1) {
types_to_use.push(valid_test_type);
}
});
if (argv.help || types_to_use.length === 0) {
console.log('\n' + optimist.help());
process.exit();
}
var is_valid_file = function (file) {
if (file.match(/buster/)) {
return false;
}
for (var i = 0; i < types_to_use.length; i++) {
var test_type = types_to_use[i];
var ext = test_type + ".js";
if (file.indexOf(ext) !== -1) {
return true;
}
}
return false;
};
function run(cb) {
walk_dir('test', is_valid_file, function (err, files) {
if (err) { return cb(err); }
files.forEach(function (file) {
mocha.addFile(file);
});
cb();
});
}
run(function (err) {
if (err) { throw err; }
mocha.run(function (failures) {
process.exit(failures);
});
});

11
test/stores/memory.unit.js

@ -0,0 +1,11 @@
var support = require('../support');
var memory_store = require('../../lib/stores/memory');
describe("memory store", function () {
describe("instantiating", function () {
it("lets us pass in no args", function (done) {
var memory_cache = memory_store.create();
support.test_set_get_del(memory_cache, done);
});
});
});

18
test/stores/redis.unit.js

@ -0,0 +1,18 @@
var support = require('../support');
var redis_store = require('../../lib/stores/redis');
describe("redis store", function () {
describe("instantiating", function () {
it("lets us pass in a db arg", function (done) {
// Not sure how to prove that it uses the specified db in this test,
// but it does.
var redis_cache = redis_store.create({db: 2});
support.test_set_get_del(redis_cache, done);
});
it("lets us pass in no args", function (done) {
var redis_cache = redis_store.create();
support.test_set_get_del(redis_cache, done);
});
});
});

75
test/support.js

@ -1,9 +1,10 @@
var fs = require('fs');
var util = require('util'); var util = require('util');
var assert = require('assert'); var assert = require('assert');
var support = { var support = {
random: { random: {
string: function(str_len) { string: function (str_len) {
str_len = str_len || 8; str_len = str_len || 8;
var chars = "abcdefghiklmnopqrstuvwxyz"; var chars = "abcdefghiklmnopqrstuvwxyz";
var random_str = ''; var random_str = '';
@ -15,7 +16,7 @@ var support = {
} }
}, },
check_err: function(err) { check_err: function (err) {
if (err) { if (err) {
var msg; var msg;
@ -28,23 +29,81 @@ var support = {
} }
var error = new Error(msg); var error = new Error(msg);
var stack = app_trace().split('\n');
stack.unshift(error.message);
error.stack = stack.join('\n');
throw error; throw error;
} }
}, },
assert_between: function(actual, lower, upper) { assert_between: function (actual, lower, upper) {
assert.ok(actual >= lower, "Expected " + actual + " to be >= " + lower); assert.ok(actual >= lower, "Expected " + actual + " to be >= " + lower);
assert.ok(actual <= upper, "Expected " + actual + " to be <= " + upper); assert.ok(actual <= upper, "Expected " + actual + " to be <= " + upper);
}, },
assert_within: function(actual, expected, delta) { assert_within: function (actual, expected, delta) {
var lower = expected - delta; var lower = expected - delta;
var upper = expected + delta; var upper = expected + delta;
this.assert_between(actual, lower, upper); this.assert_between(actual, lower, upper);
},
walk_dir: function (dir, validation_function, cb) {
if (arguments.length === 2) {
cb = validation_function;
validation_function = null;
}
var results = [];
fs.readdir(dir, function (err, list) {
if (err) { return cb(err); }
var pending = list.length;
if (!pending) { return cb(null, results); }
list.forEach(function (file) {
file = dir + '/' + file;
fs.stat(file, function (err, stat) {
if (stat && stat.isDirectory()) {
support.walk_dir(file, validation_function, function (err, res) {
results = results.concat(res);
if (!--pending) { cb(null, results); }
});
} else {
if (typeof validation_function === 'function') {
if (validation_function(file)) {
results.push(file);
}
} else {
results.push(file);
}
if (!--pending) { cb(null, results); }
}
});
});
});
},
test_set_get_del: function (cache, cb) {
var key = 'TEST' + support.random.string();
var val = support.random.string();
cache.set(key, val, function (err) {
if (err) { return cb(err); }
cache.get(key, function (err, result) {
if (err) { return cb(err); }
assert.equal(result, val);
cache.del(key, function (err) {
if (err) { return cb(err); }
cache.get(key, function (err, result) {
if (err) { return cb(err); }
assert.ok(!result);
cb();
});
});
});
});
} }
}; };

Loading…
Cancel
Save