From b419565d62e76dc2c33964bd7b27ba1e586bfa40 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 07:48:36 -0600 Subject: [PATCH 01/15] Add mocks command line argument --- bin/blueoak-server.js | 13 ++++++++++++- index.js | 5 ++++- package.json | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/bin/blueoak-server.js b/bin/blueoak-server.js index 2f219ac..9fab3de 100755 --- a/bin/blueoak-server.js +++ b/bin/blueoak-server.js @@ -3,10 +3,21 @@ * Copyright (c) 2015-2016 PointSource, LLC. * MIT Licensed */ +var parseArgs = require('minimist'); var server = require('../'); +// parse arguments +var argv = parseArgs(process.argv.slice(2)); + +// convert mocks from CSV into an array +var mocks = argv.mocks || argv.m; +if (mocks) { + mocks = mocks.split(','); +} + server.init({ - appDir: process.cwd() + appDir: process.cwd(), + mocks: mocks }, function(err) { if (err) { console.warn('Startup failed', err); diff --git a/index.js b/index.js index 7d31303..3fc08be 100644 --- a/index.js +++ b/index.js @@ -32,6 +32,10 @@ module.exports.init = function (opts, callback) { global.__appDir = opts.appDir; } + if (opts.mocks) { + global.__mocks = opts.mocks; + } + //Load the bootstrap services first (config and logging) since they're only needed for the master initServices({bootstrap: true}, function (err) { if (err) { @@ -362,4 +366,3 @@ function checkNodeVersion(logger) { module.exports.testUtility = function () { return require('./testlib/util'); }; - diff --git a/package.json b/package.json index 9846dbf..3a5668e 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "express-static": "^1.0.3", "jsonwebtoken": "^5.4.1", "lodash": "^3.9.3", + "minimist": "^1.2.0", "multer": "^1.1.0", "node-cache": "^3.0.0", "node-statsd": "^0.1.1", From bba06c92cdf0636f58304f8163f4a9524e9407e7 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 07:50:46 -0600 Subject: [PATCH 02/15] Load mock services when appropriate --- lib/loader.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/loader.js b/lib/loader.js index 1fa910e..97c9a19 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -161,6 +161,15 @@ function createLoader() { debug('Loading services from %s', dir); di.iterateOverJsFiles(dir, function(dir, file) { var modPath = path.resolve(dir, file); + + // load mock if appropriate + if (global.__mocks && global.__mocks.includes(file.substring(0, file.indexOf('.js')))) { + var mock = path.resolve(global.__appDir, 'mocks', file); + if (mock) { + modPath = mock; + } + } + var mod; try { mod = subRequire(modPath); //subRequire inserts the _id field @@ -486,11 +495,11 @@ function createLoader() { } /* - * Strips the prefix + suffix underscores from the service name, allowing you to inject the service - * without having to think of an alternative name. For example, you can inject the logger service + * Strips the prefix + suffix underscores from the service name, allowing you to inject the service + * without having to think of an alternative name. For example, you can inject the logger service * as "_logger_" and still be free to assign the service to the variable "logger". - * - * camelCamel strings are then coverted into dash-case. Based off of: + * + * camelCamel strings are then coverted into dash-case. Based off of: * https://github.com/epeli/underscore.string/blob/master/dasherize.js */ function normalizeServiceName(str) { From e254f78a369fdda28eb61ae1d9181252cb539e19 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 07:51:04 -0600 Subject: [PATCH 03/15] Add test for mocks --- .../fixtures/server12/config/default.json | 9 ++++++++ .../fixtures/server12/mocks/dummyservice.js | 8 +++++++ .../server12/services/dummyservice.js | 7 ++++++ test/integration/launchUtil.js | 13 +++++++---- test/integration/testLoader.js | 23 ++++++++++++++++++- 5 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 test/integration/fixtures/server12/config/default.json create mode 100644 test/integration/fixtures/server12/mocks/dummyservice.js create mode 100644 test/integration/fixtures/server12/services/dummyservice.js diff --git a/test/integration/fixtures/server12/config/default.json b/test/integration/fixtures/server12/config/default.json new file mode 100644 index 0000000..15ef21c --- /dev/null +++ b/test/integration/fixtures/server12/config/default.json @@ -0,0 +1,9 @@ +{ + "express": { + "port": "5000" + }, + + "cluster": { + "maxWorkers": 1 + } +} diff --git a/test/integration/fixtures/server12/mocks/dummyservice.js b/test/integration/fixtures/server12/mocks/dummyservice.js new file mode 100644 index 0000000..db25979 --- /dev/null +++ b/test/integration/fixtures/server12/mocks/dummyservice.js @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function(logger) { + logger.info('Dummy Service Mock initialized'); + process.exit(0); // exit test here +}; diff --git a/test/integration/fixtures/server12/services/dummyservice.js b/test/integration/fixtures/server12/services/dummyservice.js new file mode 100644 index 0000000..7030421 --- /dev/null +++ b/test/integration/fixtures/server12/services/dummyservice.js @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function() { + throw new Error('Dummy Service initialized'); +}; diff --git a/test/integration/launchUtil.js b/test/integration/launchUtil.js index a33a668..359a570 100644 --- a/test/integration/launchUtil.js +++ b/test/integration/launchUtil.js @@ -27,9 +27,11 @@ exports.launch = function (fixtureName, opts, done) { opts.exec = '../../bin/blueoak-server.js'; } + var args = opts.mocks ? ['--mocks', opts.mocks] : []; + var bosPath = path.resolve(__dirname, opts.exec); output = ''; - lastLaunch = spawner(execer + bosPath, + lastLaunch = spawner(execer + bosPath, args, { 'cwd': path.resolve(__dirname, 'fixtures/' + fixtureName), 'env': opts.env @@ -42,13 +44,15 @@ exports.launch = function (fixtureName, opts, done) { } ); setTimeout(function () { - // stack traces usually start with 'Error:', if there's that pattern, return it - output = /^Error:*/m.test(output) ? output : null; + if (!opts.fullOutput) { + // stack traces usually start with 'Error:', if there's that pattern, return it + output = /^Error:*/m.test(output) ? output : null; + } done(output); }, 4000); }; -exports.finish = function (done) { +exports.finish = function (done) { if (process.platform === 'win32') { child_process.exec('taskkill /PID ' + lastLaunch.pid + ' /T /F'); } @@ -65,4 +69,3 @@ exports.finish = function (done) { }); } }; - diff --git a/test/integration/testLoader.js b/test/integration/testLoader.js index 36ef8d7..ef4e2c6 100644 --- a/test/integration/testLoader.js +++ b/test/integration/testLoader.js @@ -79,4 +79,25 @@ describe('SERVER11 - middleware should get loaded from node modules', function ( done(); }); }); -}); \ No newline at end of file +}); + +describe('SERVER12 - mocks should get loaded when specified by the --mocks command line argument', function () { + this.timeout(5000); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and load mocks', function (done) { + util.launch('server12', + { + appDir: path.resolve(__dirname, 'fixtures/server12'), + mocks: 'dummyservice', + fullOutput: true + }, function(output) { + assert.ok(output.indexOf('Dummy Service Mock initialized') > -1); + assert.ok(output.indexOf('Dummy Service initialized') < 0); + done(); + }); + }); +}); From 2a3501705a8de01d54763fcd55f911212453eb60 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 08:03:56 -0600 Subject: [PATCH 04/15] Use lodash to support older JS runtimes --- lib/loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/loader.js b/lib/loader.js index 97c9a19..8da1873 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -163,7 +163,7 @@ function createLoader() { var modPath = path.resolve(dir, file); // load mock if appropriate - if (global.__mocks && global.__mocks.includes(file.substring(0, file.indexOf('.js')))) { + if (global.__mocks && _.includes(global.__mocks, file.substring(0, file.indexOf('.js')))) { var mock = path.resolve(global.__appDir, 'mocks', file); if (mock) { modPath = mock; From ec59917f5646a68b599ae721211ccdc4220782f5 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 10:08:39 -0600 Subject: [PATCH 05/15] Prepare to support mocking things other than services --- bin/blueoak-server.js | 8 ++++---- index.js | 4 ++-- lib/loader.js | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/bin/blueoak-server.js b/bin/blueoak-server.js index 9fab3de..ebfa370 100755 --- a/bin/blueoak-server.js +++ b/bin/blueoak-server.js @@ -10,14 +10,14 @@ var server = require('../'); var argv = parseArgs(process.argv.slice(2)); // convert mocks from CSV into an array -var mocks = argv.mocks || argv.m; -if (mocks) { - mocks = mocks.split(','); +var mockServices = argv['mock-services']; +if (mockServices) { + mockServices = mockServices.split(','); } server.init({ appDir: process.cwd(), - mocks: mocks + mockServices: mockServices }, function(err) { if (err) { console.warn('Startup failed', err); diff --git a/index.js b/index.js index 3fc08be..2c91323 100644 --- a/index.js +++ b/index.js @@ -32,8 +32,8 @@ module.exports.init = function (opts, callback) { global.__appDir = opts.appDir; } - if (opts.mocks) { - global.__mocks = opts.mocks; + if (opts.mockServices) { + global.__mockServices = opts.mockServices; } //Load the bootstrap services first (config and logging) since they're only needed for the master diff --git a/lib/loader.js b/lib/loader.js index 8da1873..31d4b26 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -163,10 +163,12 @@ function createLoader() { var modPath = path.resolve(dir, file); // load mock if appropriate - if (global.__mocks && _.includes(global.__mocks, file.substring(0, file.indexOf('.js')))) { - var mock = path.resolve(global.__appDir, 'mocks', file); - if (mock) { - modPath = mock; + if (global.__mockServices && + _.includes(global.__mockServices, file.substring(0, file.indexOf('.js'))) + ) { + var mockPath = path.resolve(global.__appDir, 'tests', 'bos-mocks', 'services', file); + if (mockPath) { + modPath = mockPath; } } From be88f405b1b0f57d72e628bcd3ddfe50c9ee77a8 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 10:09:22 -0600 Subject: [PATCH 06/15] Improve mock tests --- .../fixtures/server12/handlers/petstore.js | 12 ++ .../server12/services/dummyservice.js | 7 -- .../fixtures/server12/services/pet-service.js | 14 +++ .../fixtures/server12/swagger/petstore.json | 110 ++++++++++++++++++ .../bos-mocks/services/pet-service.js} | 8 +- test/integration/launchUtil.js | 8 +- test/integration/testLoader.js | 31 +++-- 7 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 test/integration/fixtures/server12/handlers/petstore.js delete mode 100644 test/integration/fixtures/server12/services/dummyservice.js create mode 100644 test/integration/fixtures/server12/services/pet-service.js create mode 100644 test/integration/fixtures/server12/swagger/petstore.json rename test/integration/fixtures/server12/{mocks/dummyservice.js => tests/bos-mocks/services/pet-service.js} (61%) diff --git a/test/integration/fixtures/server12/handlers/petstore.js b/test/integration/fixtures/server12/handlers/petstore.js new file mode 100644 index 0000000..f711e8e --- /dev/null +++ b/test/integration/fixtures/server12/handlers/petstore.js @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +var service; +exports.init = function (logger, petService) { + service = petService; +}; + +exports.getPets = function (req, res, next) { + res.send(service.getPets()); +}; diff --git a/test/integration/fixtures/server12/services/dummyservice.js b/test/integration/fixtures/server12/services/dummyservice.js deleted file mode 100644 index 7030421..0000000 --- a/test/integration/fixtures/server12/services/dummyservice.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) 2015-2016 PointSource, LLC. - * MIT Licensed - */ -exports.init = function() { - throw new Error('Dummy Service initialized'); -}; diff --git a/test/integration/fixtures/server12/services/pet-service.js b/test/integration/fixtures/server12/services/pet-service.js new file mode 100644 index 0000000..71b6a63 --- /dev/null +++ b/test/integration/fixtures/server12/services/pet-service.js @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function() { + throw new Error('Pet Service initialized'); +}; + +exports.getPets = function() { + return { + id: 1, + name: 'service pet' + }; +}; diff --git a/test/integration/fixtures/server12/swagger/petstore.json b/test/integration/fixtures/server12/swagger/petstore.json new file mode 100644 index 0000000..f6c18f8 --- /dev/null +++ b/test/integration/fixtures/server12/swagger/petstore.json @@ -0,0 +1,110 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + "termsOfService": "http://helloreverb.com/terms/", + "contact": { + "name": "Wordnik API Team", + "email": "foo@example.com", + "url": "http://madskristensen.net" + }, + "license": { + "name": "MIT", + "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" + } + }, + "host": "petstore.swagger.wordnik.com", + "basePath": "/api", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/pets": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "getPets", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + } + } + }, + "definitions": { + "pet": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "newPet": { + "allOf": [ + { + "$ref": "#/definitions/pet" + }, + { + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + } + } + } + ] + }, + "errorModel": { + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } +} diff --git a/test/integration/fixtures/server12/mocks/dummyservice.js b/test/integration/fixtures/server12/tests/bos-mocks/services/pet-service.js similarity index 61% rename from test/integration/fixtures/server12/mocks/dummyservice.js rename to test/integration/fixtures/server12/tests/bos-mocks/services/pet-service.js index db25979..86b4fba 100644 --- a/test/integration/fixtures/server12/mocks/dummyservice.js +++ b/test/integration/fixtures/server12/tests/bos-mocks/services/pet-service.js @@ -4,5 +4,11 @@ */ exports.init = function(logger) { logger.info('Dummy Service Mock initialized'); - process.exit(0); // exit test here +}; + +exports.getPets = function() { + return { + id: 99, + name: 'mock pet' + }; }; diff --git a/test/integration/launchUtil.js b/test/integration/launchUtil.js index 359a570..42564e4 100644 --- a/test/integration/launchUtil.js +++ b/test/integration/launchUtil.js @@ -27,7 +27,7 @@ exports.launch = function (fixtureName, opts, done) { opts.exec = '../../bin/blueoak-server.js'; } - var args = opts.mocks ? ['--mocks', opts.mocks] : []; + var args = opts.mockServices ? ['--mock-services', opts.mockServices] : []; var bosPath = path.resolve(__dirname, opts.exec); output = ''; @@ -44,10 +44,8 @@ exports.launch = function (fixtureName, opts, done) { } ); setTimeout(function () { - if (!opts.fullOutput) { - // stack traces usually start with 'Error:', if there's that pattern, return it - output = /^Error:*/m.test(output) ? output : null; - } + // stack traces usually start with 'Error:', if there's that pattern, return it + output = /^Error:*/m.test(output) ? output : null; done(output); }, 4000); }; diff --git a/test/integration/testLoader.js b/test/integration/testLoader.js index ef4e2c6..dfa750b 100644 --- a/test/integration/testLoader.js +++ b/test/integration/testLoader.js @@ -2,7 +2,8 @@ * Copyright (c) 2015-2016 PointSource, LLC. * MIT Licensed */ -var assert = require('assert'), +var request = require('request'), + assert = require('assert'), util = require('./launchUtil'), path = require('path'); @@ -81,23 +82,29 @@ describe('SERVER11 - middleware should get loaded from node modules', function ( }); }); -describe('SERVER12 - mocks should get loaded when specified by the --mocks command line argument', function () { +describe('SERVER12 - mock services should get loaded when specified by the --mock-services CLI argument', function () { this.timeout(5000); + before(function (done) { + util.launch('server12', + { + appDir: path.resolve(__dirname, 'fixtures/server12'), + mockServices: 'pet-service' + }, + done + ); + }); + after(function (done) { util.finish(done); }); it('Launch server and load mocks', function (done) { - util.launch('server12', - { - appDir: path.resolve(__dirname, 'fixtures/server12'), - mocks: 'dummyservice', - fullOutput: true - }, function(output) { - assert.ok(output.indexOf('Dummy Service Mock initialized') > -1); - assert.ok(output.indexOf('Dummy Service initialized') < 0); - done(); - }); + request('http://localhost:' + (process.env.PORT || 5000) + '/api/pets', function(err, resp, body) { + assert.equal(null, err); + var json = JSON.parse(body); + assert.equal('mock pet', json.name); + done(); + }); }); }); From 6f25a0a06ff2afa41fad42d98621a106ec47bf69 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 11:04:47 -0600 Subject: [PATCH 07/15] Use commander instead of minimist for cli option parsing --- bin/blueoak-server.js | 28 +++++++++++++++++----------- package.json | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/bin/blueoak-server.js b/bin/blueoak-server.js index ebfa370..ecc9e68 100755 --- a/bin/blueoak-server.js +++ b/bin/blueoak-server.js @@ -3,21 +3,15 @@ * Copyright (c) 2015-2016 PointSource, LLC. * MIT Licensed */ -var parseArgs = require('minimist'); +var cli = require('commander'); +var pkg = require('../package.json'); var server = require('../'); -// parse arguments -var argv = parseArgs(process.argv.slice(2)); - -// convert mocks from CSV into an array -var mockServices = argv['mock-services']; -if (mockServices) { - mockServices = mockServices.split(','); -} +cli = parseOptions(); server.init({ appDir: process.cwd(), - mockServices: mockServices + mockServices: cli.mockServices }, function(err) { if (err) { console.warn('Startup failed', err); @@ -25,5 +19,17 @@ server.init({ var logger = this.services.get('logger'); logger.info('Server started'); } - }); + +function parseOptions() { + // parse cli options + cli.version(pkg.version) + .option('--mock-services ', 'comma separated list of service names to mock', toList) + .parse(process.argv); + + return cli; +} + +function toList(val) { + return val.split(','); +} diff --git a/package.json b/package.json index 3a5668e..c4a7fdb 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "base64url": "^1.0.4", "body-parser": "^1.12.4", "cfenv": "^1.0.0", + "commander": "^2.11.0", "config": "^1.21.0", "cookie-session": "^1.2.0", "cors": "^2.6.0", @@ -50,7 +51,6 @@ "express-static": "^1.0.3", "jsonwebtoken": "^5.4.1", "lodash": "^3.9.3", - "minimist": "^1.2.0", "multer": "^1.1.0", "node-cache": "^3.0.0", "node-statsd": "^0.1.1", From b7b815d9301498e74b8b50713a7a7f582a8e4172 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 11:05:23 -0600 Subject: [PATCH 08/15] Use test directory instead of tests --- lib/loader.js | 2 +- .../server12/{tests => test}/bos-mocks/services/pet-service.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test/integration/fixtures/server12/{tests => test}/bos-mocks/services/pet-service.js (100%) diff --git a/lib/loader.js b/lib/loader.js index 31d4b26..91b31a7 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -166,7 +166,7 @@ function createLoader() { if (global.__mockServices && _.includes(global.__mockServices, file.substring(0, file.indexOf('.js'))) ) { - var mockPath = path.resolve(global.__appDir, 'tests', 'bos-mocks', 'services', file); + var mockPath = path.resolve(global.__appDir, 'test', 'bos-mocks', 'services', file); if (mockPath) { modPath = mockPath; } diff --git a/test/integration/fixtures/server12/tests/bos-mocks/services/pet-service.js b/test/integration/fixtures/server12/test/bos-mocks/services/pet-service.js similarity index 100% rename from test/integration/fixtures/server12/tests/bos-mocks/services/pet-service.js rename to test/integration/fixtures/server12/test/bos-mocks/services/pet-service.js From 4e42609d32873758a0a200fc7fc3d2c270976de2 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 11:19:42 -0600 Subject: [PATCH 09/15] Add documentation for mocking services --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe936d8..e074395 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,9 @@ Projects use the following directory structure. │ ├── services/ │ ├── middleware/ │ ├── swagger/ +│ ├── test/ +| | ├── bos-mocks/ +| | | ├── services/ ``` #### Handlers @@ -75,7 +78,7 @@ exports.getResult = function(num) { return num; } }; - + ``` We want to use that service from our handler, so we include `fizzbuzz` as a parameter of the `init` function. @@ -90,7 +93,7 @@ exports.init = function(app, fizzbuzz) { result: fizzbuzz.getResult(num) }); }); - + } ``` #### Third-party Services @@ -149,6 +152,10 @@ At a high-level, BlueOak Server's Swagger support provides the following: * Multiple top-level Swagger API definitions supporting delivery of multiple API base paths * Publishing of the fully compiled Swagger spec for input to by tools such as [`Swagger-UI`](http://swagger.io/swagger-ui/) and [`swagger-commander`](https://www.npmjs.com/package/swagger-commander) +#### Mocking + +Services can be mocked for testing by creating a mock service in the `test/bos-mocks/services` directory. The mock service file name should match the file name of the service you wish to mock. The implementation of a mock service is no different than a normal service implementation. After you have implemented your mock services, you can instruct BlueOak Server to use them by specifying them as a comma-separated list in the `--mock-services` command line argument. For example: `blueoak-server --mock-services service1,service2` + ### Installation ```bash From cf7cd717d03ced521f1c24e93cee53c9dc6db7e2 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 12:34:02 -0600 Subject: [PATCH 10/15] Improve and add mock tests --- package.json | 3 +- .../fixtures/server12/handlers/petstore.js | 16 +++- .../pet-service1.js} | 6 +- .../{pet-service.js => pet-service2.js} | 8 +- .../fixtures/server12/swagger/petstore.json | 30 +++++- .../test/bos-mocks/services/pet-service1.js | 14 +++ .../test/bos-mocks/services/pet-service2.js | 14 +++ test/integration/testLoader.js | 93 ++++++++++++++----- 8 files changed, 148 insertions(+), 36 deletions(-) rename test/integration/fixtures/server12/{test/bos-mocks/services/pet-service.js => services/pet-service1.js} (63%) rename test/integration/fixtures/server12/services/{pet-service.js => pet-service2.js} (50%) create mode 100644 test/integration/fixtures/server12/test/bos-mocks/services/pet-service1.js create mode 100644 test/integration/fixtures/server12/test/bos-mocks/services/pet-service2.js diff --git a/package.json b/package.json index c4a7fdb..06fdf1e 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,8 @@ "eslint": "^3.3.1", "istanbul": "^0.4.1", "mocha": "^3.0.2", - "mocha-jenkins-reporter": "^0.1.8" + "mocha-jenkins-reporter": "^0.1.8", + "request-promise": "^4.2.1" }, "files": [ "bin", diff --git a/test/integration/fixtures/server12/handlers/petstore.js b/test/integration/fixtures/server12/handlers/petstore.js index f711e8e..ca15b2d 100644 --- a/test/integration/fixtures/server12/handlers/petstore.js +++ b/test/integration/fixtures/server12/handlers/petstore.js @@ -2,11 +2,17 @@ * Copyright (c) 2015-2016 PointSource, LLC. * MIT Licensed */ -var service; -exports.init = function (logger, petService) { - service = petService; +var service1; +var service2; +exports.init = function (logger, petService1, petService2) { + service1 = petService1; + service2 = petService2; }; -exports.getPets = function (req, res, next) { - res.send(service.getPets()); +exports.getPets1 = function (req, res, next) { + res.send(service1.getPets()); +}; + +exports.getPets2 = function (req, res, next) { + res.send(service2.getPets()); }; diff --git a/test/integration/fixtures/server12/test/bos-mocks/services/pet-service.js b/test/integration/fixtures/server12/services/pet-service1.js similarity index 63% rename from test/integration/fixtures/server12/test/bos-mocks/services/pet-service.js rename to test/integration/fixtures/server12/services/pet-service1.js index 86b4fba..7061627 100644 --- a/test/integration/fixtures/server12/test/bos-mocks/services/pet-service.js +++ b/test/integration/fixtures/server12/services/pet-service1.js @@ -3,12 +3,12 @@ * MIT Licensed */ exports.init = function(logger) { - logger.info('Dummy Service Mock initialized'); + logger.info('Pet Service1 initialized'); }; exports.getPets = function() { return { - id: 99, - name: 'mock pet' + id: 1, + name: 'service1 pet' }; }; diff --git a/test/integration/fixtures/server12/services/pet-service.js b/test/integration/fixtures/server12/services/pet-service2.js similarity index 50% rename from test/integration/fixtures/server12/services/pet-service.js rename to test/integration/fixtures/server12/services/pet-service2.js index 71b6a63..29379e6 100644 --- a/test/integration/fixtures/server12/services/pet-service.js +++ b/test/integration/fixtures/server12/services/pet-service2.js @@ -2,13 +2,13 @@ * Copyright (c) 2015-2016 PointSource, LLC. * MIT Licensed */ -exports.init = function() { - throw new Error('Pet Service initialized'); +exports.init = function(logger) { + logger.info('Pet Service2 initialized'); }; exports.getPets = function() { return { - id: 1, - name: 'service pet' + id: 2, + name: 'service2 pet' }; }; diff --git a/test/integration/fixtures/server12/swagger/petstore.json b/test/integration/fixtures/server12/swagger/petstore.json index f6c18f8..2a92660 100644 --- a/test/integration/fixtures/server12/swagger/petstore.json +++ b/test/integration/fixtures/server12/swagger/petstore.json @@ -27,10 +27,36 @@ "application/json" ], "paths": { - "/pets": { + "/pets1": { "get": { "description": "Returns a user based on a single ID, if the user does not have access to the pet", - "operationId": "getPets", + "operationId": "getPets1", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + } + }, + "/pets2": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "getPets2", "produces": [ "application/json", "application/xml", diff --git a/test/integration/fixtures/server12/test/bos-mocks/services/pet-service1.js b/test/integration/fixtures/server12/test/bos-mocks/services/pet-service1.js new file mode 100644 index 0000000..f2e9dd3 --- /dev/null +++ b/test/integration/fixtures/server12/test/bos-mocks/services/pet-service1.js @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function(logger) { + logger.info('Dummy Service1 Mock initialized'); +}; + +exports.getPets = function() { + return { + id: 991, + name: 'mock1 pet' + }; +}; diff --git a/test/integration/fixtures/server12/test/bos-mocks/services/pet-service2.js b/test/integration/fixtures/server12/test/bos-mocks/services/pet-service2.js new file mode 100644 index 0000000..99c7279 --- /dev/null +++ b/test/integration/fixtures/server12/test/bos-mocks/services/pet-service2.js @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function(logger) { + logger.info('Dummy Service2 Mock initialized'); +}; + +exports.getPets = function() { + return { + id: 992, + name: 'mock2 pet' + }; +}; diff --git a/test/integration/testLoader.js b/test/integration/testLoader.js index dfa750b..3743f32 100644 --- a/test/integration/testLoader.js +++ b/test/integration/testLoader.js @@ -2,7 +2,7 @@ * Copyright (c) 2015-2016 PointSource, LLC. * MIT Licensed */ -var request = require('request'), +var request = require('request-promise'), assert = require('assert'), util = require('./launchUtil'), path = require('path'); @@ -82,29 +82,80 @@ describe('SERVER11 - middleware should get loaded from node modules', function ( }); }); -describe('SERVER12 - mock services should get loaded when specified by the --mock-services CLI argument', function () { - this.timeout(5000); +describe('SERVER12 - single mock service should get loaded when specified by the --mock-services CLI argument', + function () { + this.timeout(5000); + + before(function (done) { + util.launch('server12', + { + appDir: path.resolve(__dirname, 'fixtures/server12'), + mockServices: 'pet-service1' + }, + done + ); + }); - before(function (done) { - util.launch('server12', - { - appDir: path.resolve(__dirname, 'fixtures/server12'), - mockServices: 'pet-service' - }, - done - ); - }); + after(function (done) { + util.finish(done); + }); - after(function (done) { - util.finish(done); + it('Launch server and load mocks', function (done) { + var req = { + uri: 'http://localhost:' + (process.env.PORT || 5000) + '/api/pets1', + json: true + }; + request(req) + .then(function(res) { + assert.equal('mock1 pet', res.name); + req.uri = 'http://localhost:' + (process.env.PORT || 5000) + '/api/pets2'; + return request(req); + }) + .then(function(res) { + assert.equal('service2 pet', res.name); + done(); + }) + .catch(function(err) { + done(err); + }); + }); }); - it('Launch server and load mocks', function (done) { - request('http://localhost:' + (process.env.PORT || 5000) + '/api/pets', function(err, resp, body) { - assert.equal(null, err); - var json = JSON.parse(body); - assert.equal('mock pet', json.name); - done(); +describe('SERVER12 - multiple mock services should get loaded when specified by the --mock-services CLI argument', + function () { + this.timeout(5000); + + before(function (done) { + util.launch('server12', + { + appDir: path.resolve(__dirname, 'fixtures/server12'), + mockServices: 'pet-service1,pet-service2' + }, + done + ); + }); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and load mocks', function (done) { + var req = { + uri: 'http://localhost:' + (process.env.PORT || 5000) + '/api/pets1', + json: true + }; + request(req) + .then(function(res) { + assert.equal('mock1 pet', res.name); + req.uri = 'http://localhost:' + (process.env.PORT || 5000) + '/api/pets2'; + return request(req); + }) + .then(function(res) { + assert.equal('mock2 pet', res.name); + done(); + }) + .catch(function(err) { + done(err); + }); }); }); -}); From 73d42356a0c4b695cb4d809b2e94817c7f1f32da Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 13:51:58 -0600 Subject: [PATCH 11/15] Refactor to add support for mocking service modules --- lib/loader.js | 13 +- lib/subRequire.js | 11 +- .../fixtures/server12/handlers/petstore.js | 8 +- .../node_modules/pet-service-module/index.js | 14 ++ .../fixtures/server12/swagger/petstore.json | 26 +++ .../bos-mocks/services/pet-service-module.js | 14 ++ test/integration/testLoader.js | 187 +++++++++++------- 7 files changed, 185 insertions(+), 88 deletions(-) create mode 100644 test/integration/fixtures/server12/node_modules/pet-service-module/index.js create mode 100644 test/integration/fixtures/server12/test/bos-mocks/services/pet-service-module.js diff --git a/lib/loader.js b/lib/loader.js index 91b31a7..fe6662f 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -53,7 +53,7 @@ function createLoader() { //if parent id is a consumer, i.e. was registered with a prefix, //strip the prefix off since that's not part of the module name parentId = parentId.indexOf('.') > -1 ? parentId.substring(parentId.indexOf('.') + 1) : parentId; - + var mod = subRequire(id, parentId); moduleMap[id] = mod; dependencyMap[id] = normalizeServiceNames(di.getParamNames(mod.init)); @@ -161,17 +161,6 @@ function createLoader() { debug('Loading services from %s', dir); di.iterateOverJsFiles(dir, function(dir, file) { var modPath = path.resolve(dir, file); - - // load mock if appropriate - if (global.__mockServices && - _.includes(global.__mockServices, file.substring(0, file.indexOf('.js'))) - ) { - var mockPath = path.resolve(global.__appDir, 'test', 'bos-mocks', 'services', file); - if (mockPath) { - modPath = mockPath; - } - } - var mod; try { mod = subRequire(modPath); //subRequire inserts the _id field diff --git a/lib/subRequire.js b/lib/subRequire.js index e2e2352..cc455fe 100644 --- a/lib/subRequire.js +++ b/lib/subRequire.js @@ -25,6 +25,15 @@ var modulePath = {}; //maintain a list of the paths where we resolved files - ne * but also provides a way to load submodules by passing the optional parentId argument. */ module.exports = function (id, parentId) { + // load mock if appropriate + if (global.__mockServices && + _.includes(global.__mockServices, path.basename(id, path.extname(id))) + ) { + var mockPath = path.resolve(global.__appDir, 'test', 'bos-mocks', 'services', path.basename(id)); + if (mockPath) { + id = mockPath; + } + } if (id.indexOf('.js') > -1) { return loadJsFile(id); @@ -108,4 +117,4 @@ function loadFromParent(id, parentId) { var path = _.findWhere(parentMod.children, {exports: mod}).id; modulePath[id] = path; return mod; -} \ No newline at end of file +} diff --git a/test/integration/fixtures/server12/handlers/petstore.js b/test/integration/fixtures/server12/handlers/petstore.js index ca15b2d..107644e 100644 --- a/test/integration/fixtures/server12/handlers/petstore.js +++ b/test/integration/fixtures/server12/handlers/petstore.js @@ -4,9 +4,11 @@ */ var service1; var service2; -exports.init = function (logger, petService1, petService2) { +var serviceModule; +exports.init = function (logger, petService1, petService2, petServiceModule) { service1 = petService1; service2 = petService2; + serviceModule = petServiceModule; }; exports.getPets1 = function (req, res, next) { @@ -16,3 +18,7 @@ exports.getPets1 = function (req, res, next) { exports.getPets2 = function (req, res, next) { res.send(service2.getPets()); }; + +exports.getPets3 = function (req, res, next) { + res.send(serviceModule.getPets()); +} diff --git a/test/integration/fixtures/server12/node_modules/pet-service-module/index.js b/test/integration/fixtures/server12/node_modules/pet-service-module/index.js new file mode 100644 index 0000000..5b80012 --- /dev/null +++ b/test/integration/fixtures/server12/node_modules/pet-service-module/index.js @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function(logger) { + logger.info('Pet Service Module initialized'); +}; + +exports.getPets = function() { + return { + id: 993, + name: 'module pet' + }; +}; diff --git a/test/integration/fixtures/server12/swagger/petstore.json b/test/integration/fixtures/server12/swagger/petstore.json index 2a92660..895bd27 100644 --- a/test/integration/fixtures/server12/swagger/petstore.json +++ b/test/integration/fixtures/server12/swagger/petstore.json @@ -78,6 +78,32 @@ } } } + }, + "/pets3": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "getPets3", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + } } }, "definitions": { diff --git a/test/integration/fixtures/server12/test/bos-mocks/services/pet-service-module.js b/test/integration/fixtures/server12/test/bos-mocks/services/pet-service-module.js new file mode 100644 index 0000000..bfeb9b3 --- /dev/null +++ b/test/integration/fixtures/server12/test/bos-mocks/services/pet-service-module.js @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function(logger) { + logger.info('Pet Service Module Mock initialized'); +}; + +exports.getPets = function() { + return { + id: 993, + name: 'module mock pet' + }; +}; diff --git a/test/integration/testLoader.js b/test/integration/testLoader.js index 3743f32..7cf618c 100644 --- a/test/integration/testLoader.js +++ b/test/integration/testLoader.js @@ -7,80 +7,80 @@ var request = require('request-promise'), util = require('./launchUtil'), path = require('path'); -describe('SERVER6 - duplicate service name should fail on startup', function () { - this.timeout(5000); - - after(function (done) { - util.finish(done); - }); - - it('Launch server and check for failure', function (done) { - util.launch('server6', function(output) { - assert.ok(output.indexOf('already exists') > -1); - done(); - }); - }); -}); - -describe('SERVER9 - service module with invalid name should fail on startup', function () { - this.timeout(5000); - - after(function (done) { - util.finish(done); - }); - - it('Launch server and check for failure', function (done) { - util.launch('server9', function(output) { - assert.ok(output.indexOf('Names cannot contain periods') > -1); - done(); - }); - }); -}); - -describe('SERVER10 - handler with invalid name should fail on startup', function () { - this.timeout(5000); - - after(function (done) { - util.finish(done); - }); - - it('Launch server and check for failure', function (done) { - util.launch('server10', function(output) { - assert.ok(output.indexOf('Names cannot contain periods') > -1); - done(); - }); - }); -}); - -describe('SERVER10 - handler with invalid name should fail on startup', function () { - this.timeout(5000); - - after(function (done) { - util.finish(done); - }); - - it('Launch server and check for failure', function (done) { - util.launch('server10', function(output) { - assert.ok(output.indexOf('Names cannot contain periods') > -1); - done(); - }); - }); -}); - -describe('SERVER11 - middleware should get loaded from node modules', function () { - this.timeout(5000); - - after(function (done) { - util.finish(done); - }); - - it('Launch server and load middleware', function (done) { - util.launch('server11', {appDir: path.resolve(__dirname, 'fixtures/server11')}, function(output) { - assert.ok(output === null); - done(); - }); - }); -}); +// describe('SERVER6 - duplicate service name should fail on startup', function () { +// this.timeout(5000); +// +// after(function (done) { +// util.finish(done); +// }); +// +// it('Launch server and check for failure', function (done) { +// util.launch('server6', function(output) { +// assert.ok(output.indexOf('already exists') > -1); +// done(); +// }); +// }); +// }); +// +// describe('SERVER9 - service module with invalid name should fail on startup', function () { +// this.timeout(5000); +// +// after(function (done) { +// util.finish(done); +// }); +// +// it('Launch server and check for failure', function (done) { +// util.launch('server9', function(output) { +// assert.ok(output.indexOf('Names cannot contain periods') > -1); +// done(); +// }); +// }); +// }); +// +// describe('SERVER10 - handler with invalid name should fail on startup', function () { +// this.timeout(5000); +// +// after(function (done) { +// util.finish(done); +// }); +// +// it('Launch server and check for failure', function (done) { +// util.launch('server10', function(output) { +// assert.ok(output.indexOf('Names cannot contain periods') > -1); +// done(); +// }); +// }); +// }); +// +// describe('SERVER10 - handler with invalid name should fail on startup', function () { +// this.timeout(5000); +// +// after(function (done) { +// util.finish(done); +// }); +// +// it('Launch server and check for failure', function (done) { +// util.launch('server10', function(output) { +// assert.ok(output.indexOf('Names cannot contain periods') > -1); +// done(); +// }); +// }); +// }); +// +// describe('SERVER11 - middleware should get loaded from node modules', function () { +// this.timeout(5000); +// +// after(function (done) { +// util.finish(done); +// }); +// +// it('Launch server and load middleware', function (done) { +// util.launch('server11', {appDir: path.resolve(__dirname, 'fixtures/server11')}, function(output) { +// assert.ok(output === null); +// done(); +// }); +// }); +// }); describe('SERVER12 - single mock service should get loaded when specified by the --mock-services CLI argument', function () { @@ -113,6 +113,11 @@ describe('SERVER12 - single mock service should get loaded when specified by the }) .then(function(res) { assert.equal('service2 pet', res.name); + req.uri = 'http://localhost:' + (process.env.PORT || 5000) + '/api/pets3'; + return request(req); + }) + .then(function(res) { + assert.equal('module pet', res.name); done(); }) .catch(function(err) { @@ -159,3 +164,37 @@ describe('SERVER12 - multiple mock services should get loaded when specified by }); }); }); + +describe('SERVER12 - mock service modules should get loaded when specified by the --mock-services CLI argument', + function () { + this.timeout(5000); + + before(function (done) { + util.launch('server12', + { + appDir: path.resolve(__dirname, 'fixtures/server12'), + mockServices: 'pet-service-module' + }, + done + ); + }); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and load mocks', function (done) { + var req = { + uri: 'http://localhost:' + (process.env.PORT || 5000) + '/api/pets3', + json: true + }; + request(req) + .then(function(res) { + assert.equal('module mock pet', res.name); + done(); + }) + .catch(function(err) { + done(err); + }); + }); + }); From 822be83488752810a28f1782cc788d7e14afcd71 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Tue, 22 Aug 2017 14:13:26 -0600 Subject: [PATCH 12/15] Fix lint errors --- test/integration/fixtures/server12/handlers/petstore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/fixtures/server12/handlers/petstore.js b/test/integration/fixtures/server12/handlers/petstore.js index 107644e..9aa53b1 100644 --- a/test/integration/fixtures/server12/handlers/petstore.js +++ b/test/integration/fixtures/server12/handlers/petstore.js @@ -21,4 +21,4 @@ exports.getPets2 = function (req, res, next) { exports.getPets3 = function (req, res, next) { res.send(serviceModule.getPets()); -} +}; From 7b00fe09c7b6ad72deae9984eeded5287b96997f Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Fri, 25 Aug 2017 07:49:30 -0600 Subject: [PATCH 13/15] Restore tests --- test/integration/testLoader.js | 148 ++++++++++++++++----------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/test/integration/testLoader.js b/test/integration/testLoader.js index 7cf618c..dcc77d7 100644 --- a/test/integration/testLoader.js +++ b/test/integration/testLoader.js @@ -7,80 +7,80 @@ var request = require('request-promise'), util = require('./launchUtil'), path = require('path'); -// describe('SERVER6 - duplicate service name should fail on startup', function () { -// this.timeout(5000); -// -// after(function (done) { -// util.finish(done); -// }); -// -// it('Launch server and check for failure', function (done) { -// util.launch('server6', function(output) { -// assert.ok(output.indexOf('already exists') > -1); -// done(); -// }); -// }); -// }); -// -// describe('SERVER9 - service module with invalid name should fail on startup', function () { -// this.timeout(5000); -// -// after(function (done) { -// util.finish(done); -// }); -// -// it('Launch server and check for failure', function (done) { -// util.launch('server9', function(output) { -// assert.ok(output.indexOf('Names cannot contain periods') > -1); -// done(); -// }); -// }); -// }); -// -// describe('SERVER10 - handler with invalid name should fail on startup', function () { -// this.timeout(5000); -// -// after(function (done) { -// util.finish(done); -// }); -// -// it('Launch server and check for failure', function (done) { -// util.launch('server10', function(output) { -// assert.ok(output.indexOf('Names cannot contain periods') > -1); -// done(); -// }); -// }); -// }); -// -// describe('SERVER10 - handler with invalid name should fail on startup', function () { -// this.timeout(5000); -// -// after(function (done) { -// util.finish(done); -// }); -// -// it('Launch server and check for failure', function (done) { -// util.launch('server10', function(output) { -// assert.ok(output.indexOf('Names cannot contain periods') > -1); -// done(); -// }); -// }); -// }); -// -// describe('SERVER11 - middleware should get loaded from node modules', function () { -// this.timeout(5000); -// -// after(function (done) { -// util.finish(done); -// }); -// -// it('Launch server and load middleware', function (done) { -// util.launch('server11', {appDir: path.resolve(__dirname, 'fixtures/server11')}, function(output) { -// assert.ok(output === null); -// done(); -// }); -// }); -// }); +describe('SERVER6 - duplicate service name should fail on startup', function () { + this.timeout(5000); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and check for failure', function (done) { + util.launch('server6', function(output) { + assert.ok(output.indexOf('already exists') > -1); + done(); + }); + }); +}); + +describe('SERVER9 - service module with invalid name should fail on startup', function () { + this.timeout(5000); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and check for failure', function (done) { + util.launch('server9', function(output) { + assert.ok(output.indexOf('Names cannot contain periods') > -1); + done(); + }); + }); +}); + +describe('SERVER10 - handler with invalid name should fail on startup', function () { + this.timeout(5000); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and check for failure', function (done) { + util.launch('server10', function(output) { + assert.ok(output.indexOf('Names cannot contain periods') > -1); + done(); + }); + }); +}); + +describe('SERVER10 - handler with invalid name should fail on startup', function () { + this.timeout(5000); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and check for failure', function (done) { + util.launch('server10', function(output) { + assert.ok(output.indexOf('Names cannot contain periods') > -1); + done(); + }); + }); +}); + +describe('SERVER11 - middleware should get loaded from node modules', function () { + this.timeout(5000); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and load middleware', function (done) { + util.launch('server11', {appDir: path.resolve(__dirname, 'fixtures/server11')}, function(output) { + assert.ok(output === null); + done(); + }); + }); +}); describe('SERVER12 - single mock service should get loaded when specified by the --mock-services CLI argument', function () { From d7379f57ea307ab6a74f6d22d31ad9163efaab3a Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Fri, 25 Aug 2017 15:42:17 -0600 Subject: [PATCH 14/15] Add support for mocking middleware --- bin/blueoak-server.js | 6 +- index.js | 4 +- lib/loader.js | 4 +- lib/subRequire.js | 53 +++++- .../fixtures/server12/config/default.json | 3 +- .../fixtures/server12/handlers/petstore.js | 12 +- .../server12/middleware/pet-middleware1.js | 10 ++ .../server12/middleware/pet-middleware2.js | 10 ++ .../bos-mocks/middleware/pet-middleware1.js | 10 ++ .../bos-mocks/middleware/pet-middleware2.js | 10 ++ .../fixtures/server13/config/default.json | 10 ++ .../fixtures/server13/handlers/petstore.js | 24 +++ .../server13/middleware/pet-middleware.js | 3 + .../node_modules/pet-service-module/index.js | 14 ++ .../server13/services/pet-service1.js | 14 ++ .../server13/services/pet-service2.js | 14 ++ .../fixtures/server13/swagger/petstore.json | 162 ++++++++++++++++++ .../test/bos-mocks/services/pet-service1.js | 14 ++ test/integration/launchUtil.js | 18 +- test/integration/testLoader.js | 116 +++++++++++++ 20 files changed, 488 insertions(+), 23 deletions(-) create mode 100644 test/integration/fixtures/server12/middleware/pet-middleware1.js create mode 100644 test/integration/fixtures/server12/middleware/pet-middleware2.js create mode 100644 test/integration/fixtures/server12/test/bos-mocks/middleware/pet-middleware1.js create mode 100644 test/integration/fixtures/server12/test/bos-mocks/middleware/pet-middleware2.js create mode 100644 test/integration/fixtures/server13/config/default.json create mode 100644 test/integration/fixtures/server13/handlers/petstore.js create mode 100644 test/integration/fixtures/server13/middleware/pet-middleware.js create mode 100644 test/integration/fixtures/server13/node_modules/pet-service-module/index.js create mode 100644 test/integration/fixtures/server13/services/pet-service1.js create mode 100644 test/integration/fixtures/server13/services/pet-service2.js create mode 100644 test/integration/fixtures/server13/swagger/petstore.json create mode 100644 test/integration/fixtures/server13/test/bos-mocks/services/pet-service1.js diff --git a/bin/blueoak-server.js b/bin/blueoak-server.js index ecc9e68..917b7dc 100755 --- a/bin/blueoak-server.js +++ b/bin/blueoak-server.js @@ -11,7 +11,10 @@ cli = parseOptions(); server.init({ appDir: process.cwd(), - mockServices: cli.mockServices + mocks: { + services: cli.mockServices, + middleware: cli.mockMiddleware + } }, function(err) { if (err) { console.warn('Startup failed', err); @@ -25,6 +28,7 @@ function parseOptions() { // parse cli options cli.version(pkg.version) .option('--mock-services ', 'comma separated list of service names to mock', toList) + .option('--mock-middleware ', 'comma separated list of middleware to mock', toList) .parse(process.argv); return cli; diff --git a/index.js b/index.js index 2c91323..3fc08be 100644 --- a/index.js +++ b/index.js @@ -32,8 +32,8 @@ module.exports.init = function (opts, callback) { global.__appDir = opts.appDir; } - if (opts.mockServices) { - global.__mockServices = opts.mockServices; + if (opts.mocks) { + global.__mocks = opts.mocks; } //Load the bootstrap services first (config and logging) since they're only needed for the master diff --git a/lib/loader.js b/lib/loader.js index fe6662f..c44f5d7 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -53,7 +53,7 @@ function createLoader() { //if parent id is a consumer, i.e. was registered with a prefix, //strip the prefix off since that's not part of the module name parentId = parentId.indexOf('.') > -1 ? parentId.substring(parentId.indexOf('.') + 1) : parentId; - + var mod = subRequire(id, parentId); moduleMap[id] = mod; dependencyMap[id] = normalizeServiceNames(di.getParamNames(mod.init)); @@ -180,7 +180,7 @@ function createLoader() { var modPath = path.resolve(dir, file); var mod; try { - mod = require(modPath); + mod = subRequire(modPath); } catch (e) { throw new Error('Could not load ' + modPath + ': ' + e.message); } diff --git a/lib/subRequire.js b/lib/subRequire.js index cc455fe..dbe7563 100644 --- a/lib/subRequire.js +++ b/lib/subRequire.js @@ -13,6 +13,8 @@ * In this case we could call subRequire(Y, X), and it would require Y from the context of X. */ var path = require('path'), + util = require('util'), + fs = require('fs'), _ = require('lodash'); var modulePath = {}; //maintain a list of the paths where we resolved files - needed for unloading the modules @@ -25,15 +27,7 @@ var modulePath = {}; //maintain a list of the paths where we resolved files - ne * but also provides a way to load submodules by passing the optional parentId argument. */ module.exports = function (id, parentId) { - // load mock if appropriate - if (global.__mockServices && - _.includes(global.__mockServices, path.basename(id, path.extname(id))) - ) { - var mockPath = path.resolve(global.__appDir, 'test', 'bos-mocks', 'services', path.basename(id)); - if (mockPath) { - id = mockPath; - } - } + id = resolveMock(id); if (id.indexOf('.js') > -1) { return loadJsFile(id); @@ -54,6 +48,47 @@ module.exports.unload = function (id) { } }; +//Attempts to resolve mock modules +function resolveMock(id, type) { + // short circuit if no mocks are declared + if (!global.__mocks) { + return id; + } + + // recurse to resolve different mock types + if (arguments.length < 2) { + id = resolveMock(id, 'services'); + id = resolveMock(id, 'middleware'); + return id; + } + + // if the mock type declares our mock, attempt to resolve it + if (global.__mocks[type] && + _.includes(global.__mocks[type], path.basename(id, path.extname(id))) + ) { + var mockId = path.basename(id); + var mockPath = path.resolve(global.__appDir, 'test', 'bos-mocks', type, mockId); + var mockExists = fs.existsSync(mockPath) || fs.existsSync(mockPath + '.js'); + + // if we have a mock path and the mock exists, load it + // otherwise, warn the user + if (mockPath && mockExists) { + id = mockPath; + } else if (!mockExists) { + var logger = this.services.get('logger'); + if (logger) { + this.services.get('logger').warn( + util.format('The requested mock %s "%s" were not found in the mock %s directory (%s)', + type, + mockId, + type, + path.dirname(mockPath) + )); + } + } + } + return id; +} //Performs a require directly on a file //A module id is calculated based on the name of the file, e.g. diff --git a/test/integration/fixtures/server12/config/default.json b/test/integration/fixtures/server12/config/default.json index 15ef21c..4d6fa43 100644 --- a/test/integration/fixtures/server12/config/default.json +++ b/test/integration/fixtures/server12/config/default.json @@ -1,6 +1,7 @@ { "express": { - "port": "5000" + "port": "5000", + "middleware": ["pet-middleware1", "pet-middleware2"] }, "cluster": { diff --git a/test/integration/fixtures/server12/handlers/petstore.js b/test/integration/fixtures/server12/handlers/petstore.js index 9aa53b1..1fa0700 100644 --- a/test/integration/fixtures/server12/handlers/petstore.js +++ b/test/integration/fixtures/server12/handlers/petstore.js @@ -2,10 +2,11 @@ * Copyright (c) 2015-2016 PointSource, LLC. * MIT Licensed */ -var service1; -var service2; -var serviceModule; -exports.init = function (logger, petService1, petService2, petServiceModule) { +var service1, + service2, + serviceModule; + +exports.init = function (petService1, petService2, petServiceModule) { service1 = petService1; service2 = petService2; serviceModule = petServiceModule; @@ -13,12 +14,15 @@ exports.init = function (logger, petService1, petService2, petServiceModule) { exports.getPets1 = function (req, res, next) { res.send(service1.getPets()); + next(); }; exports.getPets2 = function (req, res, next) { res.send(service2.getPets()); + next(); }; exports.getPets3 = function (req, res, next) { res.send(serviceModule.getPets()); + next(); }; diff --git a/test/integration/fixtures/server12/middleware/pet-middleware1.js b/test/integration/fixtures/server12/middleware/pet-middleware1.js new file mode 100644 index 0000000..4fdee02 --- /dev/null +++ b/test/integration/fixtures/server12/middleware/pet-middleware1.js @@ -0,0 +1,10 @@ +exports.init = function (app, config, logger, callback) { + app.use(middleware); + logger.info('Pet Middleware1 initialized'); + callback(); +}; + +function middleware(req, res, next) { + res.header('x-pet-middleware1', 'pet-middleware1'); + next(); +} diff --git a/test/integration/fixtures/server12/middleware/pet-middleware2.js b/test/integration/fixtures/server12/middleware/pet-middleware2.js new file mode 100644 index 0000000..3068b21 --- /dev/null +++ b/test/integration/fixtures/server12/middleware/pet-middleware2.js @@ -0,0 +1,10 @@ +exports.init = function (app, config, logger, callback) { + app.use(middleware); + logger.info('Pet Middleware2 initialized'); + callback(); +}; + +function middleware(req, res, next) { + res.header('x-pet-middleware2', 'pet-middleware2'); + next(); +} diff --git a/test/integration/fixtures/server12/test/bos-mocks/middleware/pet-middleware1.js b/test/integration/fixtures/server12/test/bos-mocks/middleware/pet-middleware1.js new file mode 100644 index 0000000..4aee907 --- /dev/null +++ b/test/integration/fixtures/server12/test/bos-mocks/middleware/pet-middleware1.js @@ -0,0 +1,10 @@ +exports.init = function (app, config, logger, callback) { + app.use(middleware); + logger.info('Pet Middleware1 Mock initialized'); + callback(); +}; + +function middleware(req, res, next) { + res.header('x-pet-middleware1', 'pet-middleware1-mock'); + next(); +} diff --git a/test/integration/fixtures/server12/test/bos-mocks/middleware/pet-middleware2.js b/test/integration/fixtures/server12/test/bos-mocks/middleware/pet-middleware2.js new file mode 100644 index 0000000..0acd917 --- /dev/null +++ b/test/integration/fixtures/server12/test/bos-mocks/middleware/pet-middleware2.js @@ -0,0 +1,10 @@ +exports.init = function (app, config, logger, callback) { + app.use(middleware); + logger.info('Pet Middleware2 Mock initialized'); + callback(); +}; + +function middleware(req, res, next) { + res.header('x-pet-middleware2', 'pet-middleware2-mock'); + next(); +} diff --git a/test/integration/fixtures/server13/config/default.json b/test/integration/fixtures/server13/config/default.json new file mode 100644 index 0000000..ee1f161 --- /dev/null +++ b/test/integration/fixtures/server13/config/default.json @@ -0,0 +1,10 @@ +{ + "express": { + "port": "5000", + "middleware": ["pet-middleware"] + }, + + "cluster": { + "maxWorkers": 1 + } +} diff --git a/test/integration/fixtures/server13/handlers/petstore.js b/test/integration/fixtures/server13/handlers/petstore.js new file mode 100644 index 0000000..9aa53b1 --- /dev/null +++ b/test/integration/fixtures/server13/handlers/petstore.js @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +var service1; +var service2; +var serviceModule; +exports.init = function (logger, petService1, petService2, petServiceModule) { + service1 = petService1; + service2 = petService2; + serviceModule = petServiceModule; +}; + +exports.getPets1 = function (req, res, next) { + res.send(service1.getPets()); +}; + +exports.getPets2 = function (req, res, next) { + res.send(service2.getPets()); +}; + +exports.getPets3 = function (req, res, next) { + res.send(serviceModule.getPets()); +}; diff --git a/test/integration/fixtures/server13/middleware/pet-middleware.js b/test/integration/fixtures/server13/middleware/pet-middleware.js new file mode 100644 index 0000000..1321ed7 --- /dev/null +++ b/test/integration/fixtures/server13/middleware/pet-middleware.js @@ -0,0 +1,3 @@ +exports.init = function () { + +}; diff --git a/test/integration/fixtures/server13/node_modules/pet-service-module/index.js b/test/integration/fixtures/server13/node_modules/pet-service-module/index.js new file mode 100644 index 0000000..5b80012 --- /dev/null +++ b/test/integration/fixtures/server13/node_modules/pet-service-module/index.js @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function(logger) { + logger.info('Pet Service Module initialized'); +}; + +exports.getPets = function() { + return { + id: 993, + name: 'module pet' + }; +}; diff --git a/test/integration/fixtures/server13/services/pet-service1.js b/test/integration/fixtures/server13/services/pet-service1.js new file mode 100644 index 0000000..7061627 --- /dev/null +++ b/test/integration/fixtures/server13/services/pet-service1.js @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function(logger) { + logger.info('Pet Service1 initialized'); +}; + +exports.getPets = function() { + return { + id: 1, + name: 'service1 pet' + }; +}; diff --git a/test/integration/fixtures/server13/services/pet-service2.js b/test/integration/fixtures/server13/services/pet-service2.js new file mode 100644 index 0000000..29379e6 --- /dev/null +++ b/test/integration/fixtures/server13/services/pet-service2.js @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function(logger) { + logger.info('Pet Service2 initialized'); +}; + +exports.getPets = function() { + return { + id: 2, + name: 'service2 pet' + }; +}; diff --git a/test/integration/fixtures/server13/swagger/petstore.json b/test/integration/fixtures/server13/swagger/petstore.json new file mode 100644 index 0000000..895bd27 --- /dev/null +++ b/test/integration/fixtures/server13/swagger/petstore.json @@ -0,0 +1,162 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + "termsOfService": "http://helloreverb.com/terms/", + "contact": { + "name": "Wordnik API Team", + "email": "foo@example.com", + "url": "http://madskristensen.net" + }, + "license": { + "name": "MIT", + "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" + } + }, + "host": "petstore.swagger.wordnik.com", + "basePath": "/api", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/pets1": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "getPets1", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + } + }, + "/pets2": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "getPets2", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + } + }, + "/pets3": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "getPets3", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + } + } + }, + "definitions": { + "pet": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "newPet": { + "allOf": [ + { + "$ref": "#/definitions/pet" + }, + { + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + } + } + } + ] + }, + "errorModel": { + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } +} diff --git a/test/integration/fixtures/server13/test/bos-mocks/services/pet-service1.js b/test/integration/fixtures/server13/test/bos-mocks/services/pet-service1.js new file mode 100644 index 0000000..f2e9dd3 --- /dev/null +++ b/test/integration/fixtures/server13/test/bos-mocks/services/pet-service1.js @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +exports.init = function(logger) { + logger.info('Dummy Service1 Mock initialized'); +}; + +exports.getPets = function() { + return { + id: 991, + name: 'mock1 pet' + }; +}; diff --git a/test/integration/launchUtil.js b/test/integration/launchUtil.js index 42564e4..825d46a 100644 --- a/test/integration/launchUtil.js +++ b/test/integration/launchUtil.js @@ -27,7 +27,9 @@ exports.launch = function (fixtureName, opts, done) { opts.exec = '../../bin/blueoak-server.js'; } - var args = opts.mockServices ? ['--mock-services', opts.mockServices] : []; + var args = []; + args = args.concat(opts.mockServices ? ['--mock-services', opts.mockServices] : []); + args = args.concat(opts.mockMiddleware ? ['--mock-middleware', opts.mockMiddleware] : []); var bosPath = path.resolve(__dirname, opts.exec); output = ''; @@ -40,12 +42,20 @@ exports.launch = function (fixtureName, opts, done) { if (err && err.signal !== 'SIGINT') { console.warn(JSON.stringify(err, 0, 2), '\n' + stderr); } - output += stdout + stderr; } ); + + // capture output as it occurs + lastLaunch.stdout.on('data', function (data) {output += data;}); + lastLaunch.stderr.on('data', function (data) {output += data;}); + setTimeout(function () { - // stack traces usually start with 'Error:', if there's that pattern, return it - output = /^Error:*/m.test(output) ? output : null; + // default level to ERROR + var level = (opts.outputLevel || 'error').toLowerCase(); + + // filter output that doesn't match the log level (case-insensitive, multiline) + var regex = new RegExp('^' + level + ':*', 'im'); + output = regex.test(output) ? output : null; done(output); }, 4000); }; diff --git a/test/integration/testLoader.js b/test/integration/testLoader.js index dcc77d7..bbe5a6e 100644 --- a/test/integration/testLoader.js +++ b/test/integration/testLoader.js @@ -198,3 +198,119 @@ describe('SERVER12 - mock service modules should get loaded when specified by th }); }); }); + +describe('SERVER12 - single mock middleware should get loaded when specified by the --mock-middleware CLI argument', + function () { + this.timeout(5000); + + before(function (done) { + util.launch('server12', + { + appDir: path.resolve(__dirname, 'fixtures/server12'), + mockMiddleware: 'pet-middleware1' + }, + done + ); + }); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and load mocks', function (done) { + var req = { + uri: 'http://localhost:' + (process.env.PORT || 5000) + '/api/pets1', + resolveWithFullResponse: true + }; + request(req) + .then(function(res) { + assert.equal('pet-middleware1-mock', res.headers['x-pet-middleware1']); + assert.equal('pet-middleware2', res.headers['x-pet-middleware2']); + done(); + }) + .catch(function(err) { + done(err); + }); + }); + }); + +describe('SERVER12 - multiple mock middlewares should get loaded when specified by the --mock-middleware CLI argument', + function () { + this.timeout(5000); + + before(function (done) { + util.launch('server12', + { + appDir: path.resolve(__dirname, 'fixtures/server12'), + mockMiddleware: 'pet-middleware1,pet-middleware2' + }, + done + ); + }); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and load mocks', function (done) { + var req = { + uri: 'http://localhost:' + (process.env.PORT || 5000) + '/api/pets1', + resolveWithFullResponse: true + }; + request(req) + .then(function(res) { + assert.equal('pet-middleware1-mock', res.headers['x-pet-middleware1']); + assert.equal('pet-middleware2-mock', res.headers['x-pet-middleware2']); + done(); + }) + .catch(function(err) { + done(err); + }); + }); + }); + +describe('SERVER13 - user should be warned if mock service cannot be found', + function () { + this.timeout(5000); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and load mocks', function (done) { + var opts = { + appDir: path.resolve(__dirname, 'fixtures/server13'), + mockServices: 'pet-service2,pet-service-module', + outputLevel: 'WARN' + }; + + util.launch('server13', opts, function(output) { + assert.ok(output.indexOf('The requested mock services') > -1); + assert.ok(output.indexOf('were not found in the mock services directory') > -1); + done(); + }); + }); + }); + +describe('SERVER13 - user should be warned if mock middleware cannot be found', + function () { + this.timeout(5000); + + after(function (done) { + util.finish(done); + }); + + it('Launch server and load mocks', function (done) { + var opts = { + appDir: path.resolve(__dirname, 'fixtures/server13'), + mockMiddleware: 'pet-middleware', + outputLevel: 'WARN' + }; + + util.launch('server13', opts, function(output) { + assert.ok(output.indexOf('The requested mock middleware') > -1); + assert.ok(output.indexOf('were not found in the mock middleware directory') > -1); + done(); + }); + }); + }); From 2660153aea39e4baccef18721c836fd5aa722b48 Mon Sep 17 00:00:00 2001 From: Adam Bennett Date: Fri, 25 Aug 2017 16:11:15 -0600 Subject: [PATCH 15/15] Update docs --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e074395..7983a2b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Projects use the following directory structure. │ ├── swagger/ │ ├── test/ | | ├── bos-mocks/ +| | | ├── middleware/ | | | ├── services/ ``` @@ -154,7 +155,7 @@ At a high-level, BlueOak Server's Swagger support provides the following: #### Mocking -Services can be mocked for testing by creating a mock service in the `test/bos-mocks/services` directory. The mock service file name should match the file name of the service you wish to mock. The implementation of a mock service is no different than a normal service implementation. After you have implemented your mock services, you can instruct BlueOak Server to use them by specifying them as a comma-separated list in the `--mock-services` command line argument. For example: `blueoak-server --mock-services service1,service2` +Services and middleware can be mocked for testing by creating mocks in the `test/bos-mocks/services` or `test/bos-mocks/middleware` directories. The mock file name should match the file name of the service or middleware you wish to mock. The implementation of a mock is no different than a normal service or middleware implementation. After you have implemented your mocks, you can instruct BlueOak Server to use them by specifying them as a comma-separated list in the `--mock-services` or `--mock-middleware` command line arguments. For example: `blueoak-server --mock-services service1,service2 --mock-middleware middleware1,middleware2` ### Installation