Skip to content
Open
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Projects use the following directory structure.
│ ├── services/
│ ├── middleware/
│ ├── swagger/
│ ├── test/
| | ├── bos-mocks/
| | | ├── services/
```

#### Handlers
Expand Down Expand Up @@ -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.
Expand All @@ -90,7 +93,7 @@ exports.init = function(app, fizzbuzz) {
result: fizzbuzz.getResult(num)
});
});

}
```
#### Third-party Services
Expand Down Expand Up @@ -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
Expand Down
21 changes: 19 additions & 2 deletions bin/blueoak-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,33 @@
* 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(),
mockServices: cli.mockServices
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would:

mocks: {
  services: cli.mockServices
}

work?

Then it can be expanded to support middleware in the future

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I like this better.

}, function(err) {
if (err) {
console.warn('Startup failed', err);
} else {
var logger = this.services.get('logger');
logger.info('Server started');
}

});

function parseOptions() {
// parse cli options
cli.version(pkg.version)
.option('--mock-services <services>', 'comma separated list of service names to mock', toList)
.parse(process.argv);

return cli;
}

function toList(val) {
return val.split(',');
}
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ module.exports.init = function (opts, callback) {
global.__appDir = opts.appDir;
}

if (opts.mockServices) {
global.__mockServices = opts.mockServices;
}

//Load the bootstrap services first (config and logging) since they're only needed for the master
initServices({bootstrap: true}, function (err) {
if (err) {
Expand Down Expand Up @@ -362,4 +366,3 @@ function checkNodeVersion(logger) {
module.exports.testUtility = function () {
return require('./testlib/util');
};

10 changes: 5 additions & 5 deletions lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whitespace (I can't recall if this project has the requirement to strip trailing whitespace ... I may just be used to seeing that in other code)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My editor added that, but lint passes so I guess it's ok?

var mod = subRequire(id, parentId);
moduleMap[id] = mod;
dependencyMap[id] = normalizeServiceNames(di.getParamNames(mod.init));
Expand Down Expand Up @@ -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) {
Expand Down
11 changes: 10 additions & 1 deletion lib/subRequire.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on failure to find the service, would a message be helpful?
maybe like:

logger.warn(util.format('the requested mock service "%s" was not found in the mock service directory (%s)', mockDir, mockId));

(assuming the mock directory has been calculated once into a variable named mockDir and mockId = path.basename(id) was done too ... util.format should be used for safety since users can configure different loggers)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, definitely helpful.

id = mockPath;
}
}

if (id.indexOf('.js') > -1) {
return loadJsFile(id);
Expand Down Expand Up @@ -108,4 +117,4 @@ function loadFromParent(id, parentId) {
var path = _.findWhere(parentMod.children, {exports: mod}).id;
modulePath[id] = path;
return mod;
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
9 changes: 9 additions & 0 deletions test/integration/fixtures/server12/config/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"express": {
"port": "5000"
},

"cluster": {
"maxWorkers": 1
}
}
24 changes: 24 additions & 0 deletions test/integration/fixtures/server12/handlers/petstore.js
Original file line number Diff line number Diff line change
@@ -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());
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions test/integration/fixtures/server12/services/pet-service1.js
Original file line number Diff line number Diff line change
@@ -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'
};
};
14 changes: 14 additions & 0 deletions test/integration/fixtures/server12/services/pet-service2.js
Original file line number Diff line number Diff line change
@@ -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'
};
};
162 changes: 162 additions & 0 deletions test/integration/fixtures/server12/swagger/petstore.json
Original file line number Diff line number Diff line change
@@ -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": "[email protected]",
"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"
}
}
}
}
}
Loading