UNPKG

bastian

Version:

Handles partial cache misses, generically, using Redis as a cache store

225 lines (195 loc) 5.68 kB
var async = require('async'); var Redis = require('redis'); var test = require('tape'); var redis = Redis.createClient(); var request = require('./helper/request.js'); var Bastian = require('../index.js'); test('lookup(): Normal successful usage', function(t) { var cache = new Bastian(redis); cache.on('error', function(err) { t.error(err); }); redis.del([ 'TEST-cuisine-en-US-v4:1', 'TEST-cuisine-en-US-v4:2', 'TEST-cuisine-en-US-v4:3', 'TEST-cuisine-en-US-v4:4', 'TEST-cuisine-en-US-v4:5', 'TEST-cuisine-es-MX-v4:1', 'TEST-cuisine-es-MX-v4:2', 'TEST-cuisine-es-MX-v4:3', ]); var cuisineService = { getItems: function(ids, language, callback) { var testRun; if (ids[0] === 1 && ids.length === 1 && language === 'en-US') { testRun = 4; } else if (ids[0] === 1 && language === 'en-US') { testRun = 1; } else if (ids[0] === 3 && language === 'en-US') { testRun = 2; } else if (ids[0] === 1 && language === 'es-MX') { testRun = 3; } const VERSION = 'v4'; cache.lookup({ primary: 'id', keyPrefix: 'TEST-cuisine-' + language + '-' + VERSION, ids: ids, expiration: 60 * 60 * 24, handler: function(ids, cb) { var options = { url: 'http://cuisine.api.opentable.com/' + VERSION + '/cuisines/?ids=[' + ids + ']', json: true, headers: { "Accept-Language": language } }; t.comment(options.url); if (testRun === 1) { t.equal(ids[0], '1'); t.equal(ids[1], '2'); t.equal(ids[2], '3'); } else if (testRun === 2) { // Note that we're not looking for ids[0] === 3 for it has already been cached t.equal(ids[0], '4'); t.equal(ids[1], '5'); } else if (testRun === 3) { t.equal(ids[0], '1'); t.equal(ids[1], '2'); t.equal(ids[2], '3'); } else if (testRun === 4) { t.error(new Error('should not execute as all data for this request is in cache')); } request(options, function(err, response, body) { if (err) { return cb(err); } if (response.statusCode > 299) { return cb(response.statusCode); } cb(null, body); }); } }, callback); } }; async.series([ function(callback) { cuisineService.getItems([1, 2, 3], 'en-US', function(err, data) { t.error(err, 'no error'); t.equal(data[0].id, 1); t.equal(data[1].id, 2); t.equal(data[2].id, 3); callback(err, data); }); }, function(callback) { cuisineService.getItems([3, 4, 5], 'en-US', function(err, data) { t.error(err, 'no error'); t.equal(data[0].id, 3); t.equal(data[1].id, 4); t.equal(data[2].id, 5); callback(err, data); }); }, function(callback) { cuisineService.getItems([1, 2, 3], 'es-MX', function(err, data) { t.error(err, 'no error'); t.equal(data[0].id, 1); t.equal(data[1].id, 2); t.equal(data[2].id, 3); callback(err, data); }); }, function(callback) { cuisineService.getItems([1], 'en-US', function(err, data) { t.error(err, 'no error'); t.equal(data[0].id, 1); callback(err, data); }); } ], function(err, result) { t.error(err, 'no error'); t.ok(result[0]); t.ok(result[1]); t.ok(result[2]); t.ok(result[3]); redis.quit(); t.end(err); }); }); test('lookup(): No redis, go directly to handler', function(t) { var cache = new Bastian(); cache.lookup({ primary: 'id', keyPrefix: 'no-store', ids: [1, 2, 3], handler: function(ids, cb) { t.equal(ids.length, 3); cb(null, ids.map(function(id) { return { id: id, bigger: id * 10 }; })); } }, function(err, data) { t.deepEqual(data, [{id:1,bigger:10},{id:2,bigger:20},{id:3,bigger:30}], 'received data'); t.end(err); }); }); test('lookup(): When handler fails, overall operation should fail', function(t) { var cache = new Bastian(); cache.lookup({ primary: 'id', keyPrefix: 'no-store', ids: [1, 2, 3], handler: function(ids, cb) { cb(new Error('uh oh')); } }, function(err, data) { t.ok(err instanceof Error, 'end in error, no data in cache or handler'); t.end(); }); }); test('lookup(): When no IDs are provided, operation shouold qucikly return an array', function(t) { var cache = new Bastian(); cache.lookup({ primary: 'id', keyPrefix: 'no-data', ids: [], handler: function(ids, cb) { throw new Error('should not run'); } }, function(err, data) { t.deepEqual(data, [], 'asked for no data, received no data'); t.end(err); }); }); test('lookup(): When Redis.MGET fails, still run the handler', function(t) { var failureMgetRedis = { mget: function(data, cb) { setImmediate(function() { cb(new Error('mget failed')); }); } }; var cache = new Bastian(failureMgetRedis); var didEmitError = false; cache.on('error', function(err) { didEmitError = true; t.ok(err, 'does have error'); }); cache.lookup({ primary: 'id', keyPrefix: 'no-data', ids: [100], handler: function(ids, cb) { cb(null, 'good stuff'); } }, function(err, data) { t.ok(didEmitError, 'did emit error'); t.end(err); }); });