diff --git a/README.md b/README.md index fe936d8..7983a2b 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ Projects use the following directory structure. │ ├── services/ │ ├── middleware/ │ ├── swagger/ +│ ├── test/ +| | ├── bos-mocks/ +| | | ├── middleware/ +| | | ├── services/ ``` #### Handlers @@ -75,7 +79,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 +94,7 @@ exports.init = function(app, fizzbuzz) { result: fizzbuzz.getResult(num) }); }); - + } ``` #### Third-party Services @@ -149,6 +153,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 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 ```bash diff --git a/bin/blueoak-server.js b/bin/blueoak-server.js index 2f219ac..917b7dc 100755 --- a/bin/blueoak-server.js +++ b/bin/blueoak-server.js @@ -3,10 +3,18 @@ * Copyright (c) 2015-2016 PointSource, LLC. * MIT Licensed */ +var cli = require('commander'); +var pkg = require('../package.json'); var server = require('../'); +cli = parseOptions(); + server.init({ - appDir: process.cwd() + appDir: process.cwd(), + mocks: { + services: cli.mockServices, + middleware: cli.mockMiddleware + } }, function(err) { if (err) { console.warn('Startup failed', err); @@ -14,5 +22,18 @@ 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) + .option('--mock-middleware ', 'comma separated list of middleware to mock', toList) + .parse(process.argv); + + return cli; +} + +function toList(val) { + return val.split(','); +} 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/lib/loader.js b/lib/loader.js index 1fa910e..c44f5d7 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -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); } @@ -486,11 +486,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) { diff --git a/lib/subRequire.js b/lib/subRequire.js index e2e2352..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,6 +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) { + id = resolveMock(id); if (id.indexOf('.js') > -1) { return loadJsFile(id); @@ -45,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. @@ -108,4 +152,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/package.json b/package.json index 9846dbf..06fdf1e 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", @@ -73,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/config/default.json b/test/integration/fixtures/server12/config/default.json new file mode 100644 index 0000000..4d6fa43 --- /dev/null +++ b/test/integration/fixtures/server12/config/default.json @@ -0,0 +1,10 @@ +{ + "express": { + "port": "5000", + "middleware": ["pet-middleware1", "pet-middleware2"] + }, + + "cluster": { + "maxWorkers": 1 + } +} diff --git a/test/integration/fixtures/server12/handlers/petstore.js b/test/integration/fixtures/server12/handlers/petstore.js new file mode 100644 index 0000000..1fa0700 --- /dev/null +++ b/test/integration/fixtures/server12/handlers/petstore.js @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2015-2016 PointSource, LLC. + * MIT Licensed + */ +var service1, + service2, + serviceModule; + +exports.init = function (petService1, petService2, petServiceModule) { + service1 = petService1; + service2 = petService2; + serviceModule = 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/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/services/pet-service1.js b/test/integration/fixtures/server12/services/pet-service1.js new file mode 100644 index 0000000..7061627 --- /dev/null +++ b/test/integration/fixtures/server12/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/server12/services/pet-service2.js b/test/integration/fixtures/server12/services/pet-service2.js new file mode 100644 index 0000000..29379e6 --- /dev/null +++ b/test/integration/fixtures/server12/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/server12/swagger/petstore.json b/test/integration/fixtures/server12/swagger/petstore.json new file mode 100644 index 0000000..895bd27 --- /dev/null +++ b/test/integration/fixtures/server12/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/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/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/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/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 a33a668..825d46a 100644 --- a/test/integration/launchUtil.js +++ b/test/integration/launchUtil.js @@ -27,9 +27,13 @@ exports.launch = function (fixtureName, opts, done) { opts.exec = '../../bin/blueoak-server.js'; } + 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 = ''; - lastLaunch = spawner(execer + bosPath, + lastLaunch = spawner(execer + bosPath, args, { 'cwd': path.resolve(__dirname, 'fixtures/' + fixtureName), 'env': opts.env @@ -38,17 +42,25 @@ 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); }; -exports.finish = function (done) { +exports.finish = function (done) { if (process.platform === 'win32') { child_process.exec('taskkill /PID ' + lastLaunch.pid + ' /T /F'); } @@ -65,4 +77,3 @@ exports.finish = function (done) { }); } }; - diff --git a/test/integration/testLoader.js b/test/integration/testLoader.js index 36ef8d7..bbe5a6e 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-promise'), + assert = require('assert'), util = require('./launchUtil'), path = require('path'); @@ -79,4 +80,237 @@ describe('SERVER11 - middleware should get loaded from node modules', function ( done(); }); }); -}); \ No newline at end of file +}); + +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 + ); + }); + + 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); + 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) { + done(err); + }); + }); + }); + +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); + }); + }); + }); + +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); + }); + }); + }); + +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(); + }); + }); + });