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. 2
      lib/caching.js
  9. 3
      lib/multi_caching.js
  10. 4
      lib/stores/memory.js
  11. 37
      lib/stores/redis.js
  12. 28
      package.json
  13. 147
      readme.html
  14. 65
      test/caching.unit.js
  15. 79
      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. 67
      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();
});
});
});
});

2
lib/caching.js

@ -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);
} }

3
lib/multi_caching.js

@ -27,7 +27,7 @@ var multi_caching = function(caches) {
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.
@ -52,6 +52,7 @@ var multi_caching = function(caches) {
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);
}); });
}); });

4
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,

37
lib/stores/redis.js

@ -1,41 +1,12 @@
/*
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) {

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>

65
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;
@ -27,7 +26,7 @@ describe("caching", function() {
}); });
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);
@ -45,7 +44,7 @@ describe("caching", function() {
}); });
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);
@ -63,7 +62,7 @@ describe("caching", function() {
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();
}); });
@ -73,7 +72,7 @@ describe("caching", function() {
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) {
@ -121,6 +120,7 @@ describe("caching", function() {
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);
@ -136,9 +136,11 @@ describe("caching", function() {
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');
@ -166,6 +168,7 @@ describe("caching", function() {
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);
@ -211,9 +214,11 @@ describe("caching", function() {
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;
@ -240,10 +245,11 @@ describe("caching", function() {
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;
@ -263,6 +269,49 @@ 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');
}); });
}); });
@ -270,7 +319,7 @@ describe("caching", 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);
@ -282,7 +331,7 @@ describe("caching", function() {
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);

79
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;
@ -43,7 +43,7 @@ describe("multi_caching", function() {
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);
@ -79,10 +79,10 @@ 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) {
@ -160,6 +160,8 @@ 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) {
check_err(err);
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) {
@ -179,6 +181,8 @@ 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) {
check_err(err);
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) {
@ -239,6 +243,8 @@ 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) {
check_err(err);
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) {
@ -263,6 +269,8 @@ 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) {
check_err(err);
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) {
@ -287,6 +295,8 @@ 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) {
check_err(err);
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) {
@ -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);
});
});
});

67
test/support.js

@ -1,3 +1,4 @@
var fs = require('fs');
var util = require('util'); var util = require('util');
var assert = require('assert'); var assert = require('assert');
@ -28,10 +29,6 @@ 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;
} }
}, },
@ -45,6 +42,68 @@ var support = {
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