├── .eslintignore ├── .npmignore ├── .eslintrc.js ├── smoke-test ├── seed_data.txt ├── express │ ├── app.js │ ├── package.json │ └── bin │ │ └── www ├── test.bats └── run.sh ├── errors └── ember-cli-deploy-error.js ├── .travis.yml ├── .gitmodules ├── index.js ├── test ├── helpers │ └── test-api.js ├── index-test.js └── fetch-test.js ├── .gitignore ├── package.json ├── UPGRADING.md ├── fetch.js ├── CHANGELOG.md └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | smoke-test/express 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test 2 | /smoke-test 3 | .eslintrc.js 4 | .travis.yml 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "plugins": [ 8 | "mocha" 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /smoke-test/seed_data.txt: -------------------------------------------------------------------------------- 1 | FLUSHALL 2 | SET myapp:index:abc123 "this is abc123" 3 | SET myapp:index:def456 "this is def456" 4 | SET myapp:index:current "abc123" 5 | -------------------------------------------------------------------------------- /smoke-test/express/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | 4 | var nodeEmberCliDeployRedis = require('../../'); 5 | 6 | app.use('/*', nodeEmberCliDeployRedis('myapp:index', { 7 | host: '127.0.0.1' 8 | })); 9 | 10 | module.exports = app; 11 | -------------------------------------------------------------------------------- /smoke-test/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-ember-cli-deploy-redis-smoke-test", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "express": "~4.16.0", 10 | "debug": "^4.1.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /errors/ember-cli-deploy-error.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function EmberCliDeployError(message, critical) { 4 | Error.captureStackTrace(this, this.constructor); 5 | this.name = this.constructor.name; 6 | this.message = message; 7 | this.critical = critical; 8 | }; 9 | 10 | require('util').inherits(module.exports, Error); 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "6" 5 | - "8" 6 | - "10" 7 | services: 8 | - docker 9 | 10 | cache: 11 | directories: 12 | - node_modules 13 | 14 | jobs: 15 | include: 16 | - stage: lint 17 | node_js: "10" 18 | script: eslint . 19 | 20 | stages: 21 | - lint # fast fail if linting does not pass 22 | - test 23 | 24 | script: 25 | - npm test && npm run smoke-test 26 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "smoke-test/test-helper/bats-support"] 2 | path = smoke-test/test-helper/bats-support 3 | url = https://github.com/ztombol/bats-support 4 | [submodule "smoke-test/test-helper/bats-assert"] 5 | path = smoke-test/test-helper/bats-assert 6 | url = https://github.com/ztombol/bats-assert 7 | [submodule "smoke-test/test-helper/bats"] 8 | path = smoke-test/test-helper/bats 9 | url = https://github.com/sstephenson/bats 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fetchIndex = require('./fetch'); 4 | 5 | module.exports = function (keyPrefix, connectionInfo, opts) { 6 | return function(req, res, next) { 7 | return new Promise(function (resolve) { 8 | fetchIndex(req, keyPrefix, connectionInfo, opts).then(function(indexHtml) { 9 | res.status(200).send(indexHtml); 10 | resolve(); 11 | }).catch(function(err) { 12 | next(err); 13 | }); 14 | }); 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /test/helpers/test-api.js: -------------------------------------------------------------------------------- 1 | const ioRedisClientApi = { 2 | _storage: {}, 3 | get: function(key){ 4 | return Promise.resolve(this._storage[key]); 5 | }, 6 | set: function(key, value){ 7 | this._storage[key] = value; 8 | return Promise.resolve(value); 9 | }, 10 | del: function(key){ 11 | delete this._storage[key]; 12 | return Promise.resolve(); 13 | }, 14 | flushall: function(){ 15 | this._storage = {}; 16 | return Promise.resolve(); 17 | } 18 | }; 19 | 20 | const ioRedisApi = function() { 21 | return ioRedisClientApi; 22 | }; 23 | 24 | module.exports = { 25 | ioRedisClientApi: ioRedisClientApi, 26 | ioRedisApi: ioRedisApi 27 | }; 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # Smoke Testing 36 | smoke-test/express/node_modules 37 | smoke-test/express/package-lock.json 38 | -------------------------------------------------------------------------------- /smoke-test/test.bats: -------------------------------------------------------------------------------- 1 | load "$BATS_TEST_DIRNAME/test-helper/bats-support/load.bash" 2 | load "$BATS_TEST_DIRNAME/test-helper/bats-assert/load.bash" 3 | 4 | setup() { 5 | # See https://redis.io/topics/mass-insert for more information 6 | docker cp "$BATS_TEST_DIRNAME/seed_data.txt" $DOCKER_CONTAINER_NAME:/data 7 | docker exec $DOCKER_CONTAINER_NAME cat -- seed_data.txt | redis-cli --pipe 8 | } 9 | 10 | @test "it returns the current index key by default" { 11 | run curl -s localhost:3000 12 | assert_success 13 | assert_output 'this is abc123' 14 | } 15 | 16 | @test "it returns another index key when specified" { 17 | run curl -s localhost:3000/?index_key=def456 18 | assert_success 19 | assert_output 'this is def456' 20 | } 21 | 22 | @test "it returns a 500 when the specified key is not found in redis" { 23 | run curl -s localhost:3000/?index_key=ghi789 24 | assert_success 25 | assert_output --partial "Internal Server Error" 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-ember-cli-deploy-redis", 3 | "version": "1.0.1", 4 | "description": "An ExpressJS middleware to serve EmberJS apps deployed by ember-cli-deploy", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/blimmer/node-ember-cli-deploy-redis.git" 9 | }, 10 | "keywords": [ 11 | "ember-cli-deploy", 12 | "express", 13 | "redis" 14 | ], 15 | "engines": { 16 | "node": "^6 || ^8 || >=10" 17 | }, 18 | "scripts": { 19 | "test": "NODE_ENV=test ./node_modules/.bin/mocha ./test/**/*-test.js", 20 | "smoke-test": "./smoke-test/run.sh" 21 | }, 22 | "author": "Ben Limmer (http://benlimmer.com/)", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/blimmer/node-ember-cli-deploy-redis/issues" 26 | }, 27 | "homepage": "https://github.com/blimmer/node-ember-cli-deploy-redis", 28 | "devDependencies": { 29 | "chai": "^4.2.0", 30 | "eslint": "^5.9.0", 31 | "eslint-plugin-mocha": "^5.2.0", 32 | "mocha": "^5.2.0", 33 | "node-mocks-http": "^1.5.2", 34 | "rewire": "^4.0.1", 35 | "sinon": "^7.3.2", 36 | "sinon-chai": "^3.3.0" 37 | }, 38 | "dependencies": { 39 | "ioredis": "^4.2.0", 40 | "lodash": "^4.7.0", 41 | "memoizee": "^0.4.14" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /smoke-test/run.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | # This script starts up a redis database and seeds it with some fixture data 6 | # to do a quick smoke-test that things are working as expected. 7 | 8 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 9 | 10 | REDIS_DOCKER_IMAGE='redis:5-alpine' 11 | DOCKER_CONTAINER_NAME='node-ember-cli-deploy-redis-smoke-test' 12 | EXPRESS_APP_PID='' 13 | 14 | _start-redis() { 15 | echo "Starting redis container..." 16 | docker run -p 6379:6379 --name $DOCKER_CONTAINER_NAME -d $REDIS_DOCKER_IMAGE 17 | } 18 | 19 | _start-express-app() { 20 | echo "Starting express app..." 21 | pushd "$SCRIPT_DIR/express" 22 | npm install --no-package-lock 23 | NODE_ENV=production npm start & 24 | EXPRESS_APP_PID=$! 25 | 26 | echo "Waiting for express app to start up..." 27 | while ! nc -z localhost 3000; do 28 | sleep 0.1 29 | done 30 | echo "Express app is started!" 31 | 32 | popd 33 | } 34 | 35 | # If this gets more complex, we might want to use BATS 36 | # https://github.com/sstephenson/bats 37 | _test() { 38 | echo "Running tests..." 39 | DOCKER_CONTAINER_NAME=$DOCKER_CONTAINER_NAME "$SCRIPT_DIR"/test-helper/bats/bin/bats "$SCRIPT_DIR"/test.bats 40 | } 41 | 42 | _stop-redis() { 43 | echo "Killing redis container..." 44 | docker stop $DOCKER_CONTAINER_NAME > /dev/null 45 | docker rm $DOCKER_CONTAINER_NAME > /dev/null 46 | } 47 | 48 | _stop-express-app() { 49 | if [ $EXPRESS_APP_PID != "" ]; then 50 | echo "Killing express app..." 51 | kill $EXPRESS_APP_PID 52 | fi 53 | } 54 | 55 | _cleanup() { 56 | _stop-express-app 57 | _stop-redis 58 | } 59 | 60 | main() { 61 | _start-redis 62 | _start-express-app 63 | _test 64 | } 65 | 66 | trap _cleanup EXIT 67 | main 68 | -------------------------------------------------------------------------------- /test/index-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var { describe, before, beforeEach, afterEach, it } = require('mocha'); 3 | 4 | var sinon = require('sinon'); 5 | var httpMocks = require('node-mocks-http'); 6 | var rewire = require('rewire'); 7 | 8 | var middleware = rewire('../index'); 9 | var EmberCliDeployError = require('../errors/ember-cli-deploy-error'); 10 | 11 | var htmlString = '1'; 12 | 13 | describe('express middleware', function() { 14 | var sandbox, req, res, fetchIndexStub; 15 | before(function() { 16 | sandbox = sinon.createSandbox(); 17 | }); 18 | 19 | beforeEach(function() { 20 | req = httpMocks.createRequest(); 21 | res = httpMocks.createResponse(); 22 | 23 | fetchIndexStub = sandbox.stub(); 24 | middleware.__set__('fetchIndex', fetchIndexStub); 25 | }); 26 | 27 | afterEach(function() { 28 | sandbox.restore(); 29 | }); 30 | 31 | describe('success', function() { 32 | it('returns a 200 and sends the html', function(done) { 33 | fetchIndexStub.returns(Promise.resolve(htmlString)); 34 | 35 | middleware()(req, res).then(function() { 36 | expect(res.statusCode).to.equal(200); 37 | var data = res._getData(); 38 | expect(data).to.equal(htmlString); 39 | done(); 40 | }); 41 | }); 42 | }); 43 | 44 | describe('failure', function() { 45 | it('calls the `next` function with the error', function(done) { 46 | var error = new EmberCliDeployError(); 47 | fetchIndexStub.returns(Promise.reject(error)); 48 | 49 | function nextExpectation() { 50 | expect(arguments).to.have.length(1); 51 | expect(arguments[0]).to.equal(error); 52 | done(); 53 | } 54 | 55 | middleware()(req, res, nextExpectation).then(function() { 56 | done('Promise should not have resolved'); 57 | }); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /smoke-test/express/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('node-ember-cli-deploy-redis-smoke-test:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrading 2 | 3 | ## 0.4.x -> 1.x 4 | 5 | Version 1.x introduces a few changes that might affect your upgrade path. Please 6 | read this guide carefully to successfully upgrade your app. 7 | 8 | ### Node Version 9 | 10 | Previously, this library supported versions < Node 6. To conform to the 11 | [Node LTS Support Schedule](https://github.com/nodejs/Release), this library 12 | now only supports Node 6 and beyond. If you're using an older version of Node, 13 | you'll need to use a pre 1.x version of this library. 14 | 15 | ### Errors Passed Up to Application 16 | 17 | Previously the middleware would explicitly render an error if a requested 18 | revision key was not found. However, this prevented users from choosing their 19 | own behavior when an error is encountered. 20 | 21 | This change will now let the 22 | [default express error handler](https://expressjs.com/en/guide/error-handling.html#the-default-error-handler), 23 | or a custom-defined error handler manage this behavior. 24 | 25 | If you'd like to retain the existing behavior, write a small middleware function such as: 26 | 27 | ```javascript 28 | app.use(function (err, req, res, next) { 29 | res.status(500).send(err); 30 | }); 31 | ``` 32 | 33 | See [the documentation](https://expressjs.com/en/guide/error-handling.html#writing-error-handlers) 34 | for more information on writing a custom error handler. 35 | 36 | ### Bluebird Replaced with Native `Promise` 37 | 38 | If you use a custom `fetch` method, note that it will now return a native Node 39 | `Promise` instead of a `Bluebird` promise. `Bluebird` exposes some features 40 | that are not available in native `Promise`. 41 | 42 | ### `ioredis` upgrade 43 | 44 | The version of `ioredis` was upgraded from 0.1.x to 0.4.x. If you're passing 45 | more advanced configuration to ioredis, please verify that they are compatible 46 | with newer versions of `ioredis`. 47 | 48 | ### `memoizee` upgrade 49 | 50 | If you are not opting into memoization, this is will not be relevant to you. 51 | 52 | The version of `memoizee` was upgraded from 0.3.x to 0.4.x. If you're passing 53 | custom configuration parameters to `memoizee`, please confirm that they're 54 | compatible. 55 | 56 | ### `database` redis parameter 57 | 58 | A deprecation was introduced in 2016 that warned if you passed your database 59 | as `database` instead of `db`. That deprecation and backwards-compatibility 60 | has been removed in 1.x. 61 | 62 | You'll need to change invocations that look like this: 63 | 64 | ```javascript 65 | app.use('/*', nodeEmberCliDeployRedis('myapp:index', { 66 | database: 0 67 | })); 68 | ``` 69 | 70 | to use the `db` parameter: 71 | 72 | ```javascript 73 | app.use('/*', nodeEmberCliDeployRedis('myapp:index', { 74 | db: 0 75 | })); 76 | ``` 77 | -------------------------------------------------------------------------------- /fetch.js: -------------------------------------------------------------------------------- 1 | const _defaultsDeep = require('lodash/defaultsDeep'); 2 | 3 | const EmberCliDeployError = require('./errors/ember-cli-deploy-error'); 4 | 5 | const ioRedis = require('ioredis'); 6 | const memoize = require('memoizee'); 7 | let redisClient; 8 | 9 | const defaultConnectionInfo = { 10 | host: "127.0.0.1", 11 | port: 6379 12 | }; 13 | 14 | const _defaultOpts = { 15 | revisionQueryParam: 'index_key', 16 | memoize: false, 17 | memoizeOpts: { 18 | maxAge: 5000, // ms 19 | preFetch: true, 20 | max: 4, // a sane default (current pointer, current html and two indexkeys in cache) 21 | } 22 | }; 23 | 24 | let opts; 25 | function _getOpts(opts) { 26 | opts = opts || {}; 27 | return _defaultsDeep({}, opts, _defaultOpts); 28 | } 29 | 30 | let initialized = false; 31 | function _initialize(connectionInfo, passedOpts) { 32 | opts = _getOpts(passedOpts); 33 | let config = connectionInfo ? connectionInfo : defaultConnectionInfo; 34 | 35 | redisClient = new ioRedis(config); 36 | 37 | if (opts.memoize === true) { 38 | let memoizeOpts = opts.memoizeOpts; 39 | memoizeOpts.async = false; // this should never be overwritten by the consumer 40 | memoizeOpts.length = 1; 41 | 42 | redisClient.get = memoize(redisClient.get, memoizeOpts); 43 | } 44 | 45 | initialized = true; 46 | } 47 | 48 | function fetchIndex(req, keyPrefix, connectionInfo, passedOpts) { 49 | if (!initialized) { 50 | _initialize(connectionInfo, passedOpts); 51 | } 52 | 53 | let indexkey; 54 | if (req.query[opts.revisionQueryParam]) { 55 | let queryKey = req.query[opts.revisionQueryParam].replace(/[^A-Za-z0-9]/g, ''); 56 | indexkey = `${keyPrefix}:${queryKey}`; 57 | } 58 | 59 | let customIndexKeyWasSpecified = !!indexkey; 60 | function retrieveIndexKey(){ 61 | if (indexkey) { 62 | return Promise.resolve(indexkey); 63 | } else { 64 | return redisClient.get(keyPrefix + ":current").then(function(result){ 65 | if (!result) { throw new Error(); } 66 | return keyPrefix + ":" + result; 67 | }).catch(function(){ 68 | throw new EmberCliDeployError("There's no " + keyPrefix + ":current revision. The site is down.", true); 69 | }); 70 | } 71 | } 72 | 73 | return retrieveIndexKey().then(function(indexkey){ 74 | return redisClient.get(indexkey); 75 | }).then(function(indexHtml){ 76 | if (!indexHtml) { throw new Error(); } 77 | return indexHtml; 78 | }).catch(function(err){ 79 | if (err.name === 'EmberCliDeployError') { 80 | throw err; 81 | } else { 82 | throw new EmberCliDeployError("There's no " + indexkey + " revision. The site is down.", !customIndexKeyWasSpecified); 83 | } 84 | }); 85 | } 86 | 87 | module.exports = fetchIndex; 88 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.0.1](https://github.com/blimmer/node-ember-cli-deploy-redis/tree/1.0.1) (2019-08-31) 4 | [Full Changelog](https://github.com/blimmer/node-ember-cli-deploy-redis/compare/v1.0.0...1.0.1) 5 | 6 | **Merged pull requests:** 7 | 8 | - Bump lodash from 4.17.11 to 4.17.13 [\#24](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/24) ([dependabot[bot]](https://github.com/apps/dependabot)) 9 | - Bump eslint-utils from 1.3.1 to 1.4.2 [\#23](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/23) ([dependabot[bot]](https://github.com/apps/dependabot)) 10 | - Bump js-yaml from 3.12.0 to 3.13.1 [\#22](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/22) ([dependabot[bot]](https://github.com/apps/dependabot)) 11 | 12 | ## [v1.0.0](https://github.com/blimmer/node-ember-cli-deploy-redis/tree/v1.0.0) (2018-11-28) 13 | [Full Changelog](https://github.com/blimmer/node-ember-cli-deploy-redis/compare/v0.4.1...v1.0.0) 14 | 15 | **Merged pull requests:** 16 | 17 | - \[POTENITALLY BREAKING CHANGES\] Upgrade memoizee and ioredis [\#21](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/21) ([blimmer](https://github.com/blimmer)) 18 | - Use native Promise instead of Bluebird. [\#20](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/20) ([blimmer](https://github.com/blimmer)) 19 | - \[BREAKING CHANGE\] Pass error back to app from middleware instead of rendering a `500` [\#19](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/19) ([blimmer](https://github.com/blimmer)) 20 | - Convert smoke-test suite to bats [\#18](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/18) ([blimmer](https://github.com/blimmer)) 21 | - Add smoke tests [\#17](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/17) ([blimmer](https://github.com/blimmer)) 22 | - Upgrade dev dependencies, supported node \(\>=6\) and replace jshint [\#16](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/16) ([blimmer](https://github.com/blimmer)) 23 | - Remove `database` deprecation and fallback. [\#15](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/15) ([blimmer](https://github.com/blimmer)) 24 | 25 | ## [v0.4.1](https://github.com/blimmer/node-ember-cli-deploy-redis/tree/v0.4.1) (2016-04-03) 26 | [Full Changelog](https://github.com/blimmer/node-ember-cli-deploy-redis/compare/0.4.0...v0.4.1) 27 | 28 | **Merged pull requests:** 29 | 30 | - Dependency upgrades. [\#13](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/13) ([blimmer](https://github.com/blimmer)) 31 | 32 | ## [0.4.0](https://github.com/blimmer/node-ember-cli-deploy-redis/tree/0.4.0) (2016-02-05) 33 | [Full Changelog](https://github.com/blimmer/node-ember-cli-deploy-redis/compare/v0.3.0...0.4.0) 34 | 35 | **Implemented enhancements:** 36 | 37 | - Throttle calls to redis.get [\#4](https://github.com/blimmer/node-ember-cli-deploy-redis/issues/4) 38 | 39 | **Merged pull requests:** 40 | 41 | - IORedis / Memoization Support [\#12](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/12) ([blimmer](https://github.com/blimmer)) 42 | 43 | ## [v0.3.0](https://github.com/blimmer/node-ember-cli-deploy-redis/tree/v0.3.0) (2015-11-08) 44 | [Full Changelog](https://github.com/blimmer/node-ember-cli-deploy-redis/compare/v0.2.0...v0.3.0) 45 | 46 | **Merged pull requests:** 47 | 48 | - ember cli deploy 0.5.0 compatibility [\#10](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/10) ([blimmer](https://github.com/blimmer)) 49 | 50 | ## [v0.2.0](https://github.com/blimmer/node-ember-cli-deploy-redis/tree/v0.2.0) (2015-07-08) 51 | [Full Changelog](https://github.com/blimmer/node-ember-cli-deploy-redis/compare/v0.1.1...v0.2.0) 52 | 53 | **Closed issues:** 54 | 55 | - api inconsistency [\#6](https://github.com/blimmer/node-ember-cli-deploy-redis/issues/6) 56 | 57 | **Merged pull requests:** 58 | 59 | - v0.2.0 RC [\#8](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/8) ([blimmer](https://github.com/blimmer)) 60 | - Fix API inconsistency between fetch and middleware [\#7](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/7) ([knownasilya](https://github.com/knownasilya)) 61 | 62 | ## [v0.1.1](https://github.com/blimmer/node-ember-cli-deploy-redis/tree/v0.1.1) (2015-06-02) 63 | [Full Changelog](https://github.com/blimmer/node-ember-cli-deploy-redis/compare/v0.1.0...v0.1.1) 64 | 65 | **Merged pull requests:** 66 | 67 | - Expose the test api for mocking [\#5](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/5) ([ronco](https://github.com/ronco)) 68 | 69 | ## [v0.1.0](https://github.com/blimmer/node-ember-cli-deploy-redis/tree/v0.1.0) (2015-06-01) 70 | [Full Changelog](https://github.com/blimmer/node-ember-cli-deploy-redis/compare/v0.0.1...v0.1.0) 71 | 72 | **Implemented enhancements:** 73 | 74 | - Allow using as a middleware [\#2](https://github.com/blimmer/node-ember-cli-deploy-redis/issues/2) 75 | - Allow working with then-redis and node\_redis [\#1](https://github.com/blimmer/node-ember-cli-deploy-redis/issues/1) 76 | 77 | **Merged pull requests:** 78 | 79 | - Rework to have internal then-redis dependency. [\#3](https://github.com/blimmer/node-ember-cli-deploy-redis/pull/3) ([blimmer](https://github.com/blimmer)) 80 | 81 | ## [v0.0.1](https://github.com/blimmer/node-ember-cli-deploy-redis/tree/v0.0.1) (2015-05-24) 82 | 83 | 84 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-ember-cli-deploy-redis [![Build Status](https://travis-ci.org/blimmer/node-ember-cli-deploy-redis.svg?branch=master)](https://travis-ci.org/blimmer/node-ember-cli-deploy-redis) 2 | 3 | ExpressJS middleware to fetch the current (or specified) revision of your Ember App deployed by [ember-cli-deploy](https://github.com/ember-cli/ember-cli-deploy). 4 | 5 | ## Why? 6 | 7 | [ember-cli-deploy](https://github.com/ember-cli/ember-cli-deploy) is great. It allows you to run 8 | multiple versions in production at the same time and view revisions without impacting users. 9 | However, [the example provided](https://github.com/philipheinser/ember-lightning) uses [koa](http://koajs.com/) 10 | and many of us are not. This package allows you to easily fetch current and specified `index.html` 11 | revisions from [redis](http://redis.io) with [Express](expressjs.com) and other Node servers. 12 | 13 | ## Installation 14 | 15 | It's important to choose the right version of this library to match the version of 16 | ember-cli-deploy you're using. 17 | 18 | | ember-cli-deploy version | node-ember-cli-deploy-redis | 19 | |--------------------------|-----------------------------| 20 | | pre 0.5 | ^0.2.0 or lower | 21 | | 0.5 and beyond | ^0.3.0 or newer | 22 | 23 | Make sure to look at the 24 | [older documentation](https://github.com/blimmer/node-ember-cli-deploy-redis/blob/v0.2.0/README.md) 25 | if you're on a pre 0.5 ember-cli-deploy release. 26 | See [the changelog](https://github.com/blimmer/node-ember-cli-deploy-redis/blob/develop/CHANGELOG.md#030---2015-11-07) 27 | for an upgrade guide. 28 | 29 | ## Usage 30 | 31 | There are two main ways of using this library. For most simple Express servers, you'll 32 | want to simply use the middleware. However, if you need more flexibility, you'll 33 | want to use the internal `fetch` methods, with custom logic. 34 | 35 | ### ExpressJS Middleware 36 | 37 | 1. `require` the package 38 | 2. `use` the package in your app 39 | 40 | #### Example 41 | 42 | ```javascript 43 | const express = require('express'); 44 | const app = express(); 45 | 46 | const nodeEmberCliDeployRedis = require('node-ember-cli-deploy-redis'); 47 | app.use('/*', nodeEmberCliDeployRedis('myapp:index', { 48 | host: 'redis.example.org', // default is localhost 49 | port: 6929, // default is 6379 50 | password: 'passw0rd!', // default is undefined 51 | db: 0 // default is undefined 52 | })); 53 | ``` 54 | 55 |
56 | 57 | ### Custom Fetch Method 58 | 59 | 1. `require` the package 60 | 2. Use the `fetchIndex` method 61 | 3. Render the index string as you wish. 62 | 63 | #### Example 64 | 65 | ```javascript 66 | const express = require('express'); 67 | const app = express(); 68 | 69 | const fetchIndex = require('node-ember-cli-deploy-redis/fetch'); 70 | 71 | app.get('/', function(req, res) { 72 | fetchIndex(req, 'myapp:index', { 73 | host: 'redis.example.org', 74 | port: 6929, 75 | password: 'passw0rd!', 76 | db: 0 77 | }).then(function (indexHtml) { 78 | indexHtml = serverVarInjectHelper.injectServerVariables(indexHtml, req); 79 | res.status(200).send(indexHtml); 80 | }).catch(function(err) { 81 | res.status(500).send('Oh noes!\n' + err.message); 82 | }); 83 | }); 84 | ``` 85 | Check out [location-aware-ember-server](https://github.com/blimmer/location-aware-ember-server) for a running example. 86 | 87 | ## Documentation 88 | 89 | ### `nodeEmberCliDeployRedis(keyPrefix, connectionInfo, options)` (middleware constructor) 90 | 91 | * keyPrefix (required) - the application name, specified for ember deploy 92 | the keys in redis are prefaced with this name. For instance, if your redis keys are `my-app:index:current`, you'd pass `my-app:index`. 93 | * connectionInfo (required) - the configuration to connect to redis. 94 | internally, this library uses [ioredis](https://github.com/luin/ioredis), so pass a configuration supported by ioredis. please see their README for more information. 95 | * options (optional) - a hash of params to override [the defaults](https://github.com/blimmer/node-ember-cli-deploy-redis/blob/develop/README.md#options) 96 | 97 | ### `fetchIndex(request, keyPrefix, connectionInfo, options)` 98 | 99 | Arguments 100 | 101 | * request (required) - the request object 102 | the request object is used to check for the presence of `revisionQueryParam` 103 | * keyPrefix (required) - the application name, specified for ember deploy 104 | the keys in redis are prefaced with this name. For instance, if your redis keys are `my-app:index:current`, you'd pass `my-app:index`. 105 | * connectionInfo (required) - the configuration to connect to redis. 106 | internally, this library uses [ioredis](https://github.com/luin/ioredis), so pass a configuration supported by ioredis. 107 | * options (optional) - a hash of params to override [the defaults](https://github.com/blimmer/node-ember-cli-deploy-redis/blob/develop/README.md#options) 108 | 109 | Returns 110 | 111 | * a `Promise` 112 | when resolved, it returns the requested `index.html` string 113 | when failed, it returns an [EmberCliDeployError](https://github.com/blimmer/node-ember-cli-deploy-redis/blob/develop/errors/ember-cli-deploy-error.js). 114 | 115 | ### options 116 | 117 | * `revisionQueryParam` (defaults to `index_key`) 118 | the query parameter to specify a revision (e.g. `http://example.org/?index_key=abc123`). the key will be automatically prefaced with your `keyPrefix` for security. 119 | * `memoize` (defaults to `false`) 120 | enable memoizing Redis `get`s. see [the memoization section](#Memoization) for more details. 121 | * `memoizeOpts` ([see defaults](https://github.com/blimmer/node-ember-cli-deploy-redis/blob/master/fetch.js#L18)) 122 | customize memoization parameters. see [the memoization section](#Memoization) for more details. 123 | 124 | ## Memoization 125 | 126 | Since the majority of the requests will be serving the `current` version of your 127 | app, you can enable memoization to reduce the load on Redis. By default, memoization 128 | is disabled. To enable it, simply pass: 129 | 130 | ```javascript 131 | memoize: true 132 | ``` 133 | 134 | in your options hash. Additionally, you can pass options to the underlying memoization 135 | library ([memoizee](https://github.com/medikoo/memoizee)). Check out their documentation, 136 | and the [defaults](https://github.com/blimmer/node-ember-cli-deploy-redis/blob/master/fetch.js#L18) 137 | for this library. 138 | 139 | ### Example 140 | 141 | ```javascript 142 | app.use('/*', nodeEmberCliDeployRedis( 143 | 'myapp:index', 144 | { host: 'redis.example.org' }, 145 | { memoize: true }, 146 | )); 147 | ``` 148 | 149 | ## Testing 150 | 151 | In order to facilitate unit testing and/or integration testing this 152 | library exports a mockable redis api. You will need to use a 153 | dependency injection framework such as 154 | [rewire](https://github.com/jhnns/rewire) to activate this testing api. 155 | 156 | ### Usage with rewire (mocha syntax) 157 | 158 | ```javascript 159 | // my-module.js 160 | const fetchIndex = require('node-ember-cli-deploy-redis/fetch'); 161 | const indexWrapper = function(req, res) { 162 | return fetchIndex(req, 'app', { 163 | // real redis config 164 | }).then(function (indexHtml)) { 165 | // do something with index 166 | }); 167 | }; 168 | module.exports = indexWrapper; 169 | 170 | // my-module-test.js 171 | const redisTestApi = require('node-ember-cli-deploy-redis/test/helpers/test-api'); 172 | const fetchIndex = rewire('node-ember-cli-deploy-redis/fetch'); 173 | const redis = redisTestApi.ioRedisApi; 174 | const myModule = rewire('my-module'); 175 | 176 | describe('my module', function() { 177 | afterEach(function() { 178 | fetchIndex.__set__('_initialized', false); 179 | }); 180 | 181 | it('grabs my content', function() { 182 | // inject mocked content 183 | myModule.__set__('fetchIndex', fetchIndex); 184 | fetchIndex.__set__('ioRedis', redis); 185 | redis.set('app:abc123', "

hello test world

"); 186 | myModule(req, res).then(function(){ 187 | // assertions here 188 | }) 189 | }); 190 | }); 191 | ``` 192 | 193 | ## Notes 194 | 195 | * Don't create any other redis keys you don't want exposed to the public under your `keyPrefix`. 196 | 197 | ## Contributing 198 | 199 | Comments/PRs/Issues are welcome! 200 | 201 | ### Cloning 202 | 203 | This project utilizes git submodules. Please use the following command to clone: 204 | 205 | ```console 206 | git clone --recurse-submodules https://github.com/blimmer/node-ember-cli-deploy-redis.git 207 | ``` 208 | 209 | ### Running Project Unit Tests 210 | 211 | ```console 212 | npm test 213 | ``` 214 | 215 | ### Running Project Smoke Tests 216 | 217 | ```console 218 | npm run smoke-test 219 | ``` 220 | -------------------------------------------------------------------------------- /test/fetch-test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | require('chai').use(require("sinon-chai")); 3 | 4 | const { describe, it, before, after, beforeEach, afterEach } = require('mocha'); 5 | const context = describe; 6 | 7 | const sinon = require('sinon'); 8 | const rewire = require('rewire'); 9 | 10 | const fetchIndex = rewire('../fetch'); 11 | 12 | const basicReq = { 13 | query: {} 14 | }; 15 | 16 | const testApi = require('./helpers/test-api'); 17 | const redisClientApi = testApi.ioRedisClientApi; 18 | const ioRedisApi = testApi.ioRedisApi; 19 | 20 | describe('fetch', function() { 21 | let sandbox; 22 | before(function() { 23 | sandbox = sinon.createSandbox(); 24 | fetchIndex.__set__('ioRedis', ioRedisApi); 25 | }); 26 | 27 | afterEach(function() { 28 | fetchIndex.__set__('initialized', false); 29 | sandbox.restore(); 30 | }); 31 | 32 | describe('_initialize', function () { 33 | let _initialize; 34 | before(function() { 35 | _initialize = fetchIndex.__get__('_initialize'); 36 | }); 37 | after(function() { 38 | fetchIndex.__set__('ioRedis', ioRedisApi); 39 | }); 40 | 41 | describe('redis client initialize', function() { 42 | let ioRedisInitStub; 43 | beforeEach(function() { 44 | ioRedisInitStub = sandbox.stub(); 45 | fetchIndex.__set__('ioRedis', ioRedisInitStub); 46 | }); 47 | 48 | it('sets up redis (defaults)', function() { 49 | _initialize(); 50 | 51 | expect(ioRedisInitStub).to.have.been.calledOnce; 52 | expect(ioRedisInitStub).to.have.been.calledWithNew; 53 | expect(ioRedisInitStub).to.have.been.calledOnceWith({ 54 | host: "127.0.0.1", 55 | port: 6379 56 | }); 57 | }); 58 | 59 | it('sets up redis (config passed)', function() { 60 | let configString = 'redis://h:passw0rd@example.org:6929'; 61 | _initialize(configString); 62 | 63 | expect(ioRedisInitStub).to.have.been.calledOnce; 64 | expect(ioRedisInitStub).to.have.been.calledWithNew; 65 | expect(ioRedisInitStub).to.have.been.calledOnceWith(configString); 66 | }); 67 | }); 68 | 69 | describe('memoization', function() { 70 | let memoizeStub; 71 | beforeEach(function() { 72 | memoizeStub = sandbox.stub(); 73 | fetchIndex.__set__('memoize', memoizeStub); 74 | }); 75 | 76 | after(function() { 77 | fetchIndex.__set__('memoize', require('memoizee')); 78 | }); 79 | 80 | context('not enabled (default)', function() { 81 | it('does not enable memoize redis.get', function() { 82 | _initialize(); 83 | expect(memoizeStub.called).to.be.false; 84 | }); 85 | }); 86 | 87 | context('enabled', function() { 88 | it('passes default options to memoize', function() { 89 | _initialize({}, { memoize: true }); 90 | 91 | expect(memoizeStub).to.have.been.calledOnceWith(undefined, { 92 | maxAge: 5000, 93 | preFetch: true, 94 | max: 4, 95 | async: false, 96 | length: 1, 97 | }); 98 | }); 99 | 100 | it('allows overriding existing properties', function() { 101 | let myOpts = { 102 | maxAge: 10000, 103 | preFetch: 0.6, 104 | max: 2, 105 | }; 106 | 107 | _initialize({}, { 108 | memoize: true, 109 | memoizeOpts: myOpts, 110 | }); 111 | 112 | expect(memoizeStub).calledOnceWith(undefined, { 113 | maxAge: 10000, 114 | preFetch: 0.6, 115 | max: 2, 116 | async: false, 117 | length: 1, 118 | }); 119 | }); 120 | 121 | it('allows adding additional memoizee options', function() { 122 | const myDispose = function() { 123 | // some custom dispose logic because I'm a masochist 124 | }; 125 | let myOpts = { 126 | dispose: myDispose 127 | }; 128 | 129 | _initialize({}, { 130 | memoize: true, 131 | memoizeOpts: myOpts, 132 | }); 133 | 134 | expect(memoizeStub).calledOnceWith(undefined, { 135 | async: false, 136 | dispose: myDispose, 137 | length: 1, 138 | max: 4, 139 | maxAge: 5000, 140 | preFetch: true 141 | }); 142 | }); 143 | 144 | it('does not allow overriding async flag', function() { 145 | let myOpts = { 146 | async: true, 147 | }; 148 | 149 | _initialize({}, { 150 | memoize: true, 151 | memoizeOpts: myOpts, 152 | }); 153 | 154 | expect(memoizeStub).to.have.been.calledOnceWith(undefined, { 155 | maxAge: 5000, 156 | preFetch: true, 157 | max: 4, 158 | async: false, 159 | length: 1, 160 | }); 161 | }); 162 | 163 | it('does not allow overriding the length property', function() { 164 | let myOpts = { 165 | length: 2, 166 | }; 167 | 168 | _initialize({}, { 169 | memoize: true, 170 | memoizeOpts: myOpts, 171 | }); 172 | 173 | expect(memoizeStub).to.have.been.calledOnceWith(undefined, { 174 | maxAge: 5000, 175 | preFetch: true, 176 | max: 4, 177 | async: false, 178 | length: 1, 179 | }); 180 | }); 181 | }); 182 | }); 183 | 184 | describe('revisionQueryParam', function() { 185 | it('has a default revisionQueryParam', function() { 186 | _initialize(); 187 | 188 | let opts = fetchIndex.__get__('opts'); 189 | expect(opts.revisionQueryParam).to.equal('index_key'); 190 | }); 191 | 192 | it('allows override of revisionQueryParam', function() { 193 | _initialize({}, {revisionQueryParam: 'foobar'}); 194 | 195 | let opts = fetchIndex.__get__('opts'); 196 | expect(opts.revisionQueryParam).to.equal('foobar'); 197 | }); 198 | }); 199 | 200 | it('sets initialized flag', function() { 201 | expect(fetchIndex.__get__('initialized')).to.be.false; 202 | _initialize(); 203 | expect(fetchIndex.__get__('initialized')).to.be.true; 204 | }); 205 | }); 206 | 207 | describe('fetchIndex', function() { 208 | let redis, redisSpy; 209 | 210 | before(function() { 211 | redis = redisClientApi; 212 | }); 213 | 214 | beforeEach(function() { 215 | redisSpy = sandbox.spy(redis, 'get'); 216 | }); 217 | 218 | afterEach(function() { 219 | redis.flushall(); 220 | }); 221 | 222 | it('normalizes spaces in revisionQueryParam', function(done) { 223 | const req = { 224 | query: { 225 | index_key: 'abc 123' 226 | } 227 | }; 228 | 229 | redis.set('myapp:index:abc123', 'foo').then(function(){ 230 | fetchIndex(req, 'myapp:index').then(function() { 231 | expect(redisSpy).to.have.been.calledWith('myapp:index:abc123'); 232 | expect(redisSpy).to.not.have.been.calledWith('myapp:index:abc 123'); 233 | done(); 234 | }).catch(function(err) { 235 | done(err); 236 | }); 237 | }); 238 | 239 | }); 240 | 241 | it('removes special chars revisionQueryParam', function(done) { 242 | const req = { 243 | query: { 244 | index_key: 'ab@*#!c(@)123' 245 | } 246 | }; 247 | 248 | redis.set('myapp:index:abc123', 'foo').then(function(){ 249 | fetchIndex(req, 'myapp:index').then(function() { 250 | expect(redisSpy).to.have.been.calledWith('myapp:index:abc123'); 251 | expect(redisSpy).to.not.have.been.calledWith('myapp:index:ab@*#!c(@)123'); 252 | done(); 253 | }).catch(function(err) { 254 | done(err); 255 | }); 256 | }); 257 | }); 258 | 259 | it('fails the promise with a critical error if keyPrefix:current is not present', function(done) { 260 | redis.del('myapp:index:current').then(function(){ 261 | fetchIndex(basicReq, 'myapp:index').then(function() { 262 | done("Promise should not have resolved."); 263 | }).catch(function(err) { 264 | expect(redisSpy).to.have.been.calledWith('myapp:index:current'); 265 | expect(err.critical).to.be.true; 266 | done(); 267 | }); 268 | }); 269 | }); 270 | 271 | it('fails the promise with a critical error if revision pointed to by keyPrefix:current is not present', function(done) { 272 | redis.set('myapp:index:current', 'abc123').then(function(){ 273 | return redis.del('myapp:index:abc123'); 274 | }).then(function(){ 275 | fetchIndex(basicReq, 'myapp:index').then(function() { 276 | done("Promise should not have resolved."); 277 | }).catch(function(err) { 278 | expect(redisSpy).to.have.been.calledWith('myapp:index:current'); 279 | expect(redisSpy).to.have.been.calledWith('myapp:index:abc123'); 280 | expect(err.critical).to.be.true; 281 | done(); 282 | }); 283 | }); 284 | }); 285 | 286 | it('fails the promise with a non-critical error if revision requestd by query param is not present', function(done) { 287 | const req = { 288 | query: { 289 | index_key: 'abc123' 290 | } 291 | }; 292 | redis.del('myapp:index:abc123').then(function(){ 293 | fetchIndex(req, 'myapp:index').then(function() { 294 | done("Promise should not have resolved."); 295 | }).catch(function(err) { 296 | expect(redisSpy).to.have.been.calledWith('myapp:index:abc123'); 297 | expect(err.critical).to.be.false; 298 | done(); 299 | }); 300 | }); 301 | }); 302 | 303 | it('resolves the promise with the index html requested', function(done) { 304 | const currentHtmlString = '1'; 305 | Promise.all([ 306 | redis.set('myapp:index:current', 'abc123'), 307 | redis.set('myapp:index:abc123', currentHtmlString), 308 | ]).then(function(){ 309 | fetchIndex(basicReq, 'myapp:index').then(function(html) { 310 | expect(redisSpy).to.have.been.calledWith('myapp:index:current'); 311 | expect(redisSpy).to.have.been.calledWith('myapp:index:abc123'); 312 | expect(html).to.equal(currentHtmlString); 313 | done(); 314 | }).catch(function() { 315 | done("Promise should not have failed."); 316 | }); 317 | }); 318 | }); 319 | 320 | it('resolves the promise with the index html requested (specific revision)', function(done) { 321 | const currentHtmlString = '1'; 322 | const newDeployHtmlString = '2'; 323 | const req = { 324 | query: { 325 | index_key: 'def456' 326 | } 327 | }; 328 | Promise.all([ 329 | redis.set('myapp:index:current', 'abc123'), 330 | redis.set('myapp:index:abc123', currentHtmlString), 331 | redis.set('myapp:index:def456', newDeployHtmlString) 332 | ]).then(function(){ 333 | fetchIndex(req, 'myapp:index').then(function(html) { 334 | expect(redisSpy).to.not.have.been.calledWith('myapp:index:current'); 335 | expect(redisSpy).to.have.been.calledWith('myapp:index:def456'); 336 | expect(html).to.equal(newDeployHtmlString); 337 | done(); 338 | }).catch(function() { 339 | done("Promise should not have failed."); 340 | }); 341 | }); 342 | }); 343 | 344 | it('memoizes results from redis when turned on', function(done) { 345 | const currentHtmlString = '1'; 346 | Promise.all([ 347 | redis.set('myapp:index:current', 'abc123'), 348 | redis.set('myapp:index:abc123', currentHtmlString), 349 | fetchIndex(basicReq, 'myapp:index', null, { memoize: true }), 350 | fetchIndex(basicReq, 'myapp:index', null, { memoize: true }), 351 | fetchIndex(basicReq, 'myapp:index', null, { memoize: true }), 352 | ]).then(function(){ 353 | expect(redisSpy).to.have.been.calledWith('myapp:index:current'); 354 | expect(redisSpy).to.have.been.calledWith('myapp:index:abc123'); 355 | done(); 356 | }); 357 | }); 358 | }); 359 | }); 360 | --------------------------------------------------------------------------------