UNPKG

cacher

Version:

A memcached backed http cache in the form of express middleware

530 lines (482 loc) 15.7 kB
require('http').Agent.maxSockets = 50 var Cacher = require('../lib/Cacher') var MemoryCache = require('../lib/MemoryClient') var express = require('express') var supertest = require('supertest') var async = require('async') var assert = require('assert') var Client = null var clientConfig = null if (process.env.CACHER_CLIENT) { Client = require(process.env.CACHER_CLIENT) } else { Client = MemoryCache } describe('Cacher', function() { describe('Instantation', function() { it('should instantiate with no parameters and have a proper client', function() { var c = new Cacher(new Client()) assert(c.client instanceof Client) }) it('should not instantiate when passed a client that doesnt adhere to the interface', function() { try { function BadClient() {} var bc = new BadClient() var c = new Cacher(bc) } catch(e) { assert(!!e) } }) }) describe('CalcTtl', function() { var c = new Cacher(new Client()) it('should be able to compute proper ttls from cache values', function() { var MIN = 60 var HOUR = MIN * 60 var DAY = HOUR * 24 assert.equal(1, c.calcTtl('second')) assert.equal(10, c.calcTtl('seconds', 10)) assert.equal(MIN, c.calcTtl('minute')) assert.equal(MIN*2, c.calcTtl('minutes', 2)) assert.equal(HOUR, c.calcTtl('hour')) assert.equal(HOUR*2, c.calcTtl('hours', 2)) assert.equal(DAY, c.calcTtl('day')) assert.equal(DAY*2, c.calcTtl('days', 2)) assert.equal(0, c.calcTtl(0)) assert.equal(0, c.calcTtl(false)) assert.equal(0, c.calcTtl('days', 0)) }) }) describe('Override Caching', function() { var cacher = new Cacher(new Client()) cacher.noCaching = true var app = express() app.get('/', cacher.cache('day'), function(req, res) { res.send('boop') }) it('shouldnt cache when noCaching is set to true', function(done) { supertest(app) .get('/') .expect(200) .expect('Cache-Control', 'no-cache') .expect('boop') .end(function(err, res) { assert.ifError(err) supertest(app) .get('/') .expect(200) .expect('Cache-Control', 'no-cache') .expect('boop') .end(function(err, res) { assert.ifError(err) done() }) }) }) }) describe('Override TTL based on response', function() { var cacher = new Cacher(new Client()) var app = express() var errorCodes = [ 400, 401, 403, 404, 500, 504 ] app.get('/error', cacher.cache('minute'), function(req, res) { var code = Number(req.param('code')) || 400 res.send('kaboom', code) }) beforeEach(function() { // clear '/error?code=*' entries from cache before each test. errorCodes.forEach(function(httpErrorStatusCode) { cacher.invalidate('/error?code=' + httpErrorStatusCode) }) }) it('should return unaltered TTL by default, regardless of response statusCode', function(done) { assert.strictEqual(cacher.genCacheTtl({ statusCode: 200 }, 60), 60) async.each( errorCodes, function(httpErrorStatusCode, next) { assert.strictEqual(cacher.genCacheTtl({ statusCode: httpErrorStatusCode }, 60), 60) supertest(app) .get('/error') .query({ code: httpErrorStatusCode }) .expect(cacher.cacheHeader, 'false') .expect(httpErrorStatusCode, 'kaboom') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) supertest(app) .get('/error') .query({ code: httpErrorStatusCode }) .expect(cacher.cacheHeader, 'true') // 2nd request is cached .expect(httpErrorStatusCode, 'kaboom') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) next() }) }) }, done ) }) it('should use custom TTL on error responses when genCacheTtl is overridden', function(done) { // Override TTL for error responses to be 0 (i.e. not cached). cacher.genCacheTtl = function(res, origTtl) { if (errorCodes.indexOf(res.statusCode) >= 0) { return 0 } return origTtl } assert.strictEqual(cacher.genCacheTtl({ statusCode: 200 }, 60), 60) async.each( errorCodes, function(httpErrorStatusCode, next) { assert.strictEqual(cacher.genCacheTtl({ statusCode: httpErrorStatusCode }, 60), 0) supertest(app) .get('/error') .query({ code: httpErrorStatusCode }) .expect(cacher.cacheHeader, 'false') .expect(httpErrorStatusCode, 'kaboom') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) supertest(app) .get('/error') .query({ code: httpErrorStatusCode }) .expect(cacher.cacheHeader, 'false') // 2nd request is NOT cached .expect(httpErrorStatusCode, 'kaboom') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) next() }) }) }, done ) }) }) describe('Caching', function() { var cacher = new Cacher(new Client()) var app = require("./fixtures/server")(cacher) it('should cache when there is a sufficent ttl', function(done) { supertest(app) .get('/long') .expect(cacher.cacheHeader, 'false') .expect(200) .expect('long') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) supertest(app) .get('/long') .expect(cacher.cacheHeader, 'true') .expect(200) .expect('long') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) done() }) }) }) it('shouldnt cache when the ttl expires', function(done) { function secondReq() { supertest(app) .get('/short') .expect(cacher.cacheHeader, 'false') .expect(200) .expect('short') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) done() }) } supertest(app) .get('/short') .expect(cacher.cacheHeader, 'false') .expect(200) .expect('short') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) setTimeout(secondReq, 1500) }) }) it('shouldnt cache after an invalidation', function(done) { cacher.invalidate('/long', function(err) { assert.ifError(err) supertest(app) .get('/long') .expect(cacher.cacheHeader, 'false') .expect(200) .expect('long') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) supertest(app) .get('/long') .expect(cacher.cacheHeader, 'true') .expect(200) .expect('long') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) done() }) }) }) }) it('should be able to dynamically disable ignoring of request headers', function(done) { cacher.ignoreClientNoCache = false supertest(app) .get('/long') .set('Cache-Control', 'no-cache') .expect(cacher.cacheHeader, 'false') .expect(200) .expect('long') .expect('Content-Type', /text/) .end(function(err, res) { cacher.ignoreClientNoCache = true assert.ifError(err) done() }) }) it('should work with other contentTypes', function(done) { supertest(app) .get('/json') .expect(cacher.cacheHeader, 'false') .expect(200) .expect({boop: true}) .expect('Content-Type', /json/) .end(function(err, res) { assert.ifError(err) supertest(app) .get('/json') .expect(cacher.cacheHeader, 'true') .expect(200) .expect({boop: true}) .expect('Content-Type', /json/) .end(function(err, res) { assert.ifError(err) done() }) }) }) it('should preserve status codes', function(done) { supertest(app) .get('/201') .expect(cacher.cacheHeader, 'false') .expect(201) .expect('boop') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) supertest(app) .get('/201') .expect(cacher.cacheHeader, 'true') .expect(201) .expect('boop') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) done() }) }) }) it('should preserve custom headers', function(done) { supertest(app) .get('/header') .expect(cacher.cacheHeader, 'false') .expect(200) .expect('header') .expect('Content-Type', /text/) .expect('X-Custom-Header', 'boop') .end(function(err, res) { assert.ifError(err) supertest(app) .get('/header') .expect(cacher.cacheHeader, 'true') .expect(200) .expect('header') .expect('Content-Type', /text/) .expect('X-Custom-Header', 'boop') .end(function(err, res) { assert.ifError(err) done() }) }) }) it('shouldnt cache post request', function(done) { supertest(app) .post('/post') .expect(200) .expect('OK') .expect('Content-Type', /text/) .end(function(err, res) { assert(!res.header[cacher.cacheHeader]) assert.ifError(err) supertest(app) .post('/post') .expect(200) .expect('OK') .expect('Content-Type', /text/) .end(function(err, res) { assert(!res.header[cacher.cacheHeader]) assert.ifError(err) done() }) }) }) it('shouldnt cache an empty page with HEAD requests', function(done) { supertest(app) .head('/head') .expect(200) .expect('Content-Type', /text/) .end(function(err, res) { assert(!res.header[cacher.cacheHeader]) assert.ifError(err) supertest(app) .get('/head') .expect(cacher.cacheHeader, 'false') .expect('HEAD request') .expect(200) .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) supertest(app) .get('/head') .expect(cacher.cacheHeader, 'true') .expect('HEAD request') .expect(200) .expect('Content-Type', /text/) .end(function(err, res) { done() }) }) }) }) it('should still work when res.write is used', function(done) { supertest(app) .get('/write') .expect(cacher.cacheHeader, 'false') .expect(200) .expect('beep|boop') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) supertest(app) .get('/write') .expect(cacher.cacheHeader, 'true') .expect(200) .expect('beep|boop') .expect('Content-Type', /text/) .end(function(err, res) { assert.ifError(err) done() }) }) }) it('should work with attachments', function(done) { supertest(app) .get('/pdf') .expect(cacher.cacheHeader, 'false') .expect(200) .expect('Content-Type', /pdf/) .expect('Content-Length', '7945') .end(function(err, res) { assert.ifError(err) var text = res.text supertest(app) .get('/pdf') .expect(cacher.cacheHeader, 'true') .expect(200) .expect('Content-Type', /pdf/) .expect('Content-Length', '7945') .end(function(err, res) { assert.ifError(err) assert.equal(text, res.text) done() }) }) }) it('should work with files', function(done) { supertest(app) .get('/image') .expect(cacher.cacheHeader, 'false') .expect(200) .expect('Content-Type', /png/) .expect('Content-Length', '1504') .end(function(err, res) { assert.ifError(err) var text = res.text supertest(app) .get('/image') .expect(cacher.cacheHeader, 'true') .expect(200) .expect('Content-Type', /png/) .expect('Content-Length', '1504') .end(function(err, res) { assert.ifError(err) assert.equal(text, res.text) done() }) }) }) it('should be able to dynamically disable caching', function(done) { cacher.noCaching = true supertest(app) .get('/long') .expect('Cache-Control', 'no-cache') .expect(200) .end(function(err, res) { assert.ifError(err) cacher.noCaching = false supertest(app) .get('/long') .expect(cacher.cacheHeader, 'true') .expect(200) .end(function(err, res) { assert.ifError(err) done() }) }) }) it('should ignore request cache control headers by default', function(done) { supertest(app) .get('/long') .expect(200) .end(function(err, res) { assert.ifError(err) supertest(app) .get('/long') .set('Cache-Control', 'no-cache') .expect(cacher.cacheHeader, 'true') .expect(200) .end(function(err, res) { assert.ifError(err) done() }) }) }) it('should avoid key collisions when same relative url is used on two different mount points', function(done) { var collisionTest = function(expectedCache, next) { supertest(app) .get('/fooMount/nodupe') .expect(cacher.cacheHeader, expectedCache) .expect(200, 'foo') .end(function(err, res) { assert.ifError(err) supertest(app) .get('/barMount/nodupe') .expect(cacher.cacheHeader, expectedCache) .expect(200, 'bar') .end(function(err, res) { assert.ifError(err) next() }) }) } async.series([ function(next) { collisionTest('false', next) }, function(next) { collisionTest('true', next) }, function() { done() } ]) }) }) })