From a9bc08ed7757ba995f91fc527411e0a36d5f1ded Mon Sep 17 00:00:00 2001 From: Rowan Hill Date: Sun, 15 Dec 2013 17:09:50 +0000 Subject: [PATCH 1/2] Add tests for parent API config loading --- README.md | 2 +- app.js | 19 +++------- package.json | 9 ++++- src/apiConfigLoader.js | 34 +++++++++++++++++ src/jsonLoader.js | 11 ++++++ test/apiConfigLoaderSpec.js | 73 +++++++++++++++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 src/apiConfigLoader.js create mode 100644 src/jsonLoader.js create mode 100644 test/apiConfigLoaderSpec.js diff --git a/README.md b/README.md index 31ff7561..e54dd89e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ INSTALLATION INSTRUCTIONS FOR I/O DOCS From the command line type in:
  git clone http://github.com/mashery/iodocs.git
   cd iodocs
-  npm install
+  npm install --production
 
diff --git a/app.js b/app.js index c8f06403..0072b33b 100755 --- a/app.js +++ b/app.js @@ -36,7 +36,8 @@ var express = require('express'), https = require('https'), crypto = require('crypto'), redis = require('redis'), - RedisStore = require('connect-redis')(express); + RedisStore = require('connect-redis')(express), + apiConfigLoader = require('./src/apiConfigLoader.js'); // Configuration try { @@ -71,20 +72,10 @@ db.on("error", function(err) { // // Load API Configs // - -config.apiConfigDir = path.resolve(config.apiConfigDir || 'public/data'); -if (!fs.existsSync(config.apiConfigDir)) { - console.error("Could not find API config directory: " + config.apiConfigDir); - process.exit(1); -} - try { - var apisConfig = require(path.join(config.apiConfigDir, 'apiconfig.json')); - if (config.debug) { - console.log(util.inspect(apisConfig)); - } -} catch(e) { - console.error("File apiconfig.json not found or is invalid."); + var apisConfig = apiConfigLoader.load(config); +} catch (e) { + console.error(e.message); process.exit(1); } diff --git a/package.json b/package.json index d549df0e..49f25449 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,11 @@ "querystring": "0.1.0", "supervisor": ">= 0.5.x" }, - "devDependencies": {}, + "devDependencies": { + "mocha": "~1.15.1", + "should": "~2.1.1", + "sinon": "~1.7.3" + }, "main": "index", "engines": { "node": ">= 0.4.0", @@ -39,6 +43,7 @@ }, "scripts": { "start": "node_modules/.bin/supervisor -e 'js|json' app", - "startwin": "supervisor -e 'js' app" + "startwin": "supervisor -e 'js' app", + "test": "node_modules/.bin/mocha --ui bdd --reporter spec" } } diff --git a/src/apiConfigLoader.js b/src/apiConfigLoader.js new file mode 100644 index 00000000..5c62e5d1 --- /dev/null +++ b/src/apiConfigLoader.js @@ -0,0 +1,34 @@ +var fs = require('fs'), + path = require('path'), + util = require('util'), + jsonLoader = require('./jsonLoader.js'); + +/** + * Loads the APIs' configuration object from the directory specified by config.apiConfigDir. The value of + * config.apiConfigdir is also updated to be a fully resolved path. + * + * @param config {Object} I/O Docs config object + * @returns {*} + */ +exports.load = function(config) { + var apiConfigFile = '[config file not yet set]', + apisConfig; + + config.apiConfigDir = path.resolve(config.apiConfigDir || 'public/data'); + if (!fs.existsSync(config.apiConfigDir)) { + throw new Error("Could not find API config directory: " + apiConfigDir); + } + + try { + apiConfigFile = path.join(config.apiConfigDir, 'apiconfig.json'); + apisConfig = jsonLoader.load(apiConfigFile); + } catch(e) { + throw new Error("File " + apiConfigFile + " not found or is invalid."); + } + + if (config.debug) { + console.log(util.inspect(apisConfig)); + } + + return apisConfig; +}; \ No newline at end of file diff --git a/src/jsonLoader.js b/src/jsonLoader.js new file mode 100644 index 00000000..24eabd63 --- /dev/null +++ b/src/jsonLoader.js @@ -0,0 +1,11 @@ +/** + * Requires the given path and returns an object. + * + * This is a trivial wrapper for require(), used so that it can be stubbed in tests + * + * @param path {string} + * @returns {*} + */ +exports.load = function(path) { + return require(path); +}; \ No newline at end of file diff --git a/test/apiConfigLoaderSpec.js b/test/apiConfigLoaderSpec.js new file mode 100644 index 00000000..644dffc6 --- /dev/null +++ b/test/apiConfigLoaderSpec.js @@ -0,0 +1,73 @@ +var sinon = require('sinon'), + should = require('should'); + +var fs = require('fs'), + path = require('path'), + jsonLoader = require('../src/jsonLoader.js'); + +var apiConfigLoader = require('../src/apiConfigLoader.js'); + +describe("API config loader", function() { + var sandbox = sinon.sandbox.create(); + var mockApiConfig = {_name: 'mock API config'}; + + function givenPathResolves(dirPath) { + sandbox.stub(path, 'resolve').withArgs(dirPath).returns('/some/resolved/path'); + } + + beforeEach(function() { + sandbox.stub(fs, 'existsSync').withArgs('/some/resolved/path').returns(true); + sandbox.stub(path, 'join').withArgs('/some/resolved/path', 'apiconfig.json').returns('/some/resolved/path/someConfig.json'); + sandbox.stub(jsonLoader, 'load').withArgs('/some/resolved/path/someConfig.json').returns(mockApiConfig) + }); + + afterEach(function() { + sandbox.restore(); + }); + + it('defaults to loading from public/data/apiconfig.json', function() { + // given + givenPathResolves('public/data'); + var emptyConfig = {}; + + // when + var apiConfig = apiConfigLoader.load(emptyConfig); + + // then + apiConfig.should.eql(mockApiConfig); + }); + + it('can have directory specified', function() { + // given + givenPathResolves('some/config/dir'); + var config = { + apiConfigDir: 'some/config/dir' + }; + + // when + var apiConfig = apiConfigLoader.load(config); + + // then + apiConfig.should.eql(mockApiConfig); + }); + + it('throws an error if the specified config dir cannot be found', function() { + givenPathResolves('public/data'); + fs.existsSync.restore(); + sandbox.stub(fs, 'existsSync').withArgs('/some/resolved/path').returns(false); + + var loading = function() {apiConfigLoader.load(config)}; + + loading.should.throw(); + }); + + it('throws an error if loading the JSON fails', function() { + givenPathResolves('public/data'); + jsonLoader.load.restore(); + sandbox.stub(jsonLoader, 'load').throws('Something went wrong!'); + + var loading = function() {apiConfigLoader.load(config)}; + + loading.should.throw(); + }); +}); \ No newline at end of file From cd9d6f1589cffcbb4d6bf0139fe451726da942c5 Mon Sep 17 00:00:00 2001 From: Rowan Hill Date: Sun, 15 Dec 2013 18:19:48 +0000 Subject: [PATCH 2/2] Add tests for Redis setup --- app.js | 23 ++------- src/redisSetup.js | 24 +++++++++ test/redisSetupSpec.js | 111 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 20 deletions(-) create mode 100644 src/redisSetup.js create mode 100644 test/redisSetupSpec.js diff --git a/app.js b/app.js index 0072b33b..1321a000 100755 --- a/app.js +++ b/app.js @@ -35,9 +35,9 @@ var express = require('express'), http = require('http'), https = require('https'), crypto = require('crypto'), - redis = require('redis'), RedisStore = require('connect-redis')(express), - apiConfigLoader = require('./src/apiConfigLoader.js'); + apiConfigLoader = require('./src/apiConfigLoader.js'), + redisSetup = require('./src/redisSetup.js'); // Configuration try { @@ -50,24 +50,7 @@ try { // // Redis connection // -var defaultDB = '0'; -config.redis.database = config.redis.database || defaultDB; - -if (process.env.REDISTOGO_URL) { - var rtg = require("url").parse(process.env.REDISTOGO_URL); - config.redis.host = rtg.hostname; - config.redis.port = rtg.port; - config.redis.password = rtg.auth.split(":")[1]; -} - -var db = redis.createClient(config.redis.port, config.redis.host); -db.auth(config.redis.password); - -db.on("error", function(err) { - if (config.debug) { - console.log("Error " + err); - } -}); +var db = redisSetup.configure(config.redis); // // Load API Configs diff --git a/src/redisSetup.js b/src/redisSetup.js new file mode 100644 index 00000000..d98db1a0 --- /dev/null +++ b/src/redisSetup.js @@ -0,0 +1,24 @@ +var redis = require('redis'); + +exports.configure = function(redisConfig) { + var defaultDB = '0'; + redisConfig.database = redisConfig.database || defaultDB; + + if (process.env.REDISTOGO_URL) { + var rtg = require("url").parse(process.env.REDISTOGO_URL); + redisConfig.host = rtg.hostname; + redisConfig.port = rtg.port; + redisConfig.password = rtg.auth.split(":")[1]; + } + + var db = redis.createClient(redisConfig.port, redisConfig.host); + db.auth(redisConfig.password); + + db.on("error", function(err) { + if (config.debug) { + console.log("Error " + err); + } + }); + + return db; +}; \ No newline at end of file diff --git a/test/redisSetupSpec.js b/test/redisSetupSpec.js new file mode 100644 index 00000000..3723ed4b --- /dev/null +++ b/test/redisSetupSpec.js @@ -0,0 +1,111 @@ +var sinon = require('sinon'), + should = require('should'); + +var redis = require('redis'), + url = require('url'); + +var redisSetup = require('../src/redisSetup.js'); + +describe('Redis setup', function() { + var sandbox = sinon.sandbox.create(); + + var fakeClient = { + auth: function() {}, + on: function() {} + }; + + afterEach(function() { + sandbox.restore(); + }); + + it('defaults the configured database to 0', function() { + // given + var config = {}; + + // when + redisSetup.configure(config); + + // then + config.should.have.property('database', '0'); + }); + + it('respects a previously configured database property', function() { + // given + var config = {database: '1'}; + + // when + redisSetup.configure(config); + + // then + config.should.have.property('database', '1'); + }); + + it('returns a Redis client created from config', function() { + // given + var config = { + port: 12345, + host: 'some-host', + password: 'its-a-secret' + }; + sandbox.stub(redis, 'createClient').withArgs(config.port, config.host).returns(fakeClient); + var authSpy = sandbox.spy(fakeClient, 'auth'); + + // when + var client = redisSetup.configure(config); + + // then + client.should.eql(fakeClient); + authSpy.calledWith(config.password).should.be.ok; + }); + + it('overrides config values with Redis To Go values if available', function() { + // given + var config = { + port: 12345, + host: 'some-host', + password: 'its-a-secret' + }; + process.env.REDISTOGO_URL = 'some-url'; + var redisToGoConfigPassword = 'another-password'; + var redisToGoConfig = { + hostname: 'another-host', + port: 54321, + auth: 'user:' + redisToGoConfigPassword + }; + sandbox.stub(url, 'parse').withArgs(process.env.REDISTOGO_URL).returns(redisToGoConfig); + + // when + redisSetup.configure(config); + + // then + config.should.have.property('port', redisToGoConfig.port); + config.should.have.property('host', redisToGoConfig.hostname); + config.should.have.property('password', redisToGoConfigPassword); + }); + + it('returns a Redis client created from Redit To Go values if available', function() { + // given + var config = { + port: 12345, + host: 'some-host', + password: 'its-a-secret' + }; + process.env.REDISTOGO_URL = 'some-url'; + var redisToGoConfigPassword = 'another-password'; + var redisToGoConfig = { + hostname: 'another-host', + port: 54321, + auth: 'user:' + redisToGoConfigPassword + }; + sandbox.stub(url, 'parse').withArgs(process.env.REDISTOGO_URL).returns(redisToGoConfig); + sandbox.stub(redis, 'createClient').withArgs(redisToGoConfig.port, redisToGoConfig.hostname).returns(fakeClient); + var authSpy = sandbox.spy(fakeClient, 'auth'); + + // when + var client = redisSetup.configure(config); + + // then + client.should.eql(fakeClient); + authSpy.calledWith(redisToGoConfigPassword).should.be.ok; + }); +}); \ No newline at end of file