Skip to content

Commit

Permalink
Merge pull request #13 from telefonicaid/release/0.6.0
Browse files Browse the repository at this point in the history
Release/0.6.0
  • Loading branch information
JoseAntonioRodriguez committed Nov 6, 2014
2 parents 25d7247 + 41a5ca1 commit 67c5df6
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 68 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ node_modules

# npm
npm-debug.log

tests
presentation
26 changes: 25 additions & 1 deletion ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# RELEASE NOTES

## v0.6.0 / 6 Nov 2014

### API Mock

* API Mock now supports 2waySSL. Write `apimockserver` without parameters to see how to use it. Last requests objects will
include a new property named `certificate` that will convey the details of the certificate used by the client to establish
the connection.
* Mock responses may include references to request body fields when the body can be parsed as JSON or XML.
XML bodies are converted to JSON using [xml2json](https://www.npmjs.org/package/xml2json).
If the body can be parsed, it will be accessible through a `bodyJson` property.

```json
{
"method": "POST",
"path": "/",
"response": {
"statusCode": 200,
"body": "Result: {{{bodyJson.fieldName}}}"
}
}
```


## v0.5.0 / 3 Oct 2014

### Gherkin framework and reporter
Expand Down Expand Up @@ -67,7 +90,8 @@
{ minorBug: 'my-bug-id', desc: 'This is a variant with a minor bug' }
];
```


## v0.4.0 / 7 Aug 2014

### Gherkin framework and reporter
Expand Down
55 changes: 43 additions & 12 deletions bin/apimockserver
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,66 @@

'use strict';

var optimist = require('optimist');
var optimist = require('optimist')
, util = require('util')
;

var argv = optimist
.usage('\nUsage: apimockserver -a <admin_port> -p <port> [-s <ssl_port> -k <key_file> -c <cert_file>]')
.usage('\nUsage: apimockserver -a <admin_port>\n' +
' [-p <port>]\n' +
' [-s <ssl_port> -k <key_file> -c <cert_file>]\n' +
' [-2 <2wayssl_port> -k <key_file> -c <cert_file> -w <ca_file>]')
.options('a', { alias: 'admin-port', describe: 'Administration port', demand: true })
.options('p', { alias: 'port', describe: 'API Mock Server port (HTTP)', demand: true })
.options('p', { alias: 'port', describe: 'API Mock Server port (HTTP)' })
.options('s', { alias: 'ssl-port', describe: 'API Mock Server port (HTTPS)' })
.options('k', { alias: 'key', describe: 'Private key file for HTTPS server' })
.options('c', { alias: 'cert', describe: 'Certificate file for HTTPS server' })
.options('2', { alias: '2wayssl-port', describe: 'API Mock Server port (2waySSL)' })
.options('k', { alias: 'key', describe: 'Private key file for HTTPS/2waySSL server' })
.options('c', { alias: 'cert', describe: 'Certificate file for HTTPS/2waySSL server' })
.options('w', { alias: 'ca', describe: 'Authority certificate file for 2waySSL server' })
.options('t', { alias: 'timeout', describe: 'Server timeout (default: 2 min)' })
.argv;

if ((argv.a && typeof(argv.a) !== 'number') ||
(argv.p && typeof(argv.p) !== 'number') ||
(argv.s && typeof(argv.s) !== 'number') ||
(argv.k && typeof(argv.k) !== 'string') ||
(argv.c && typeof(argv.c) !== 'string') ||
(argv.s && !(argv.k && argv.c)) ||
(argv.t && typeof(argv.t) !== 'number')) {
(argv['2'] && typeof(argv['2']) !== 'number') ||
(argv.w && util.isArray(argv.w)) ||
(argv.t && typeof(argv.t) !== 'number') ||
(argv.s && !(argv.k && argv.c)) || // k and c are mandatory when s exists
(argv['2'] && !(argv.k && argv.c && argv.w))) { // k, c and w are mandatory when 2 exists
optimist.showHelp();
process.exit(1);
}

if (argv.s && argv['2']) {
if (!(util.isArray(argv.k) && argv.k.length === 2 && util.isArray(argv.c) && argv.c.length === 2)) {
optimist.showHelp();
process.exit(1);
}
} else if (argv.s || argv['2']) {
if (util.isArray(argv.k) || util.isArray(argv.c)) {
optimist.showHelp();
process.exit(1);
}
argv.k = [argv.k];
argv.c = [argv.c];
}

var settings = {};
settings.adminPort = argv.a;
settings.httpPort = argv.p;
if (argv.p) {
settings.httpPort = argv.p;
}
if (argv.s) {
settings.httpsPort = argv.s;
settings.httpsKeyPath = argv.k;
settings.httpsCertPath = argv.c;
settings.httpsKeyPath = argv.k[0];
settings.httpsCertPath = argv.c[0];
}
if (argv['2']) {
settings.twowaysslPort = argv['2'];
settings.twowaysslKeyPath = argv.k[1] || argv.k[0];
settings.twowaysslCertPath = argv.c[1] || argv.c[0];
settings.twowaysslCAPath = argv.w;
}
if (argv.t) {
settings.timeout = argv.t;
Expand Down
31 changes: 29 additions & 2 deletions lib/apimock/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var jsonschema = require('jsonschema')
, _ = require('underscore')
, extend = require('node.extend')
, schemas = require('./json-schemas')
, xml2json = require('xml2json')
, utils = require('../utils')
;

Expand Down Expand Up @@ -93,6 +94,14 @@ function _buildRequestObject(req, config, cb) {
remotePort: req.connection.remotePort
};

// Get remote certificate
if (req.connection.getPeerCertificate) {
var certificate = req.connection.getPeerCertificate();
if (certificate && Object.keys(certificate).length) {
requestObj.certificate = certificate;
}
}

var chunks = [];

req.on('data', function(chunk) {
Expand All @@ -104,6 +113,15 @@ function _buildRequestObject(req, config, cb) {
if (!config.binaryBody) {
requestObj.body = iconv.decode(bodyBuffer, requestObj.charset || 'utf-8');
requestObj.binaryBody = false;
try {
// Try to parse the body as a JSON document
requestObj.bodyJson = JSON.parse(requestObj.body);
} catch(err) {
try {
// Try to parse the body as an XML document
requestObj.bodyJson = xml2json.toJson(requestObj.body, { object: true, coerce: false, sanitize: false });
} catch(err) {}
}
} else {
requestObj.body = bodyBuffer.toString('base64');
requestObj.binaryBody = true;
Expand Down Expand Up @@ -195,9 +213,11 @@ function _unmarshallConfig(config) {

/**
* Convert query and headers to its JSON representation, since nedb does not allow keys (header names) to have dots.
* The bodyJson property needs also to be marshalled because when converting from XML to JSON, come fields could
* start with '$', which is not allowed by nedb
*
* @param lastRequest
* @param copy True if marhalled object must be a copy of the original object
* @param copy True if marshalled object must be a copy of the original object
* @private
*/
function _marshallLastRequest(lastRequest, copy) {
Expand All @@ -208,11 +228,14 @@ function _marshallLastRequest(lastRequest, copy) {
if (_lastRequest.headers) {
_lastRequest.headers = JSON.stringify(_lastRequest.headers);
}
if (_lastRequest.bodyJson) {
_lastRequest.bodyJson = JSON.stringify(_lastRequest.bodyJson);
}
return _lastRequest;
}

/**
* Convert query and headers from JSON to its javascript object representation
* Convert query, headers and bodyJson from JSON to its javascript object representation
*
* @param lastRequests
* @private
Expand All @@ -229,6 +252,10 @@ function _unmarshallLastRequests(lastRequests) {
if (lastRequest.headers) {
lastRequest.headers = JSON.parse(lastRequest.headers);
}
// Convert jsonBody from JSON to javascript object
if (lastRequest.bodyJson) {
lastRequest.bodyJson = JSON.parse(lastRequest.bodyJson);
}
});
return lastRequests;
}
Expand Down
67 changes: 47 additions & 20 deletions lib/apimock/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

module.exports = function apiMockServer(settings) {
var http = require('http')
, https = require('https')
, fs = require('fs')
, express = require('express')
, Datastore = require('nedb')
, middlewares = require('./middlewares')
Expand Down Expand Up @@ -98,30 +100,30 @@ module.exports = function apiMockServer(settings) {
})
.listen(settings.adminPort);

http.createServer(middlewares.mock.requestHandler)
.on('error', function onError(err) {
console.log('API Mock Server (HTTP) ERROR:');
console.error(err.stack);
process.exit(-102);
})
.on('listening', function onListening() {
console.log('API Mock Server (HTTP) listening on port', settings.httpPort);
})
.on('connection', function onConnection(socket) {
socket.setTimeout(context.timeout);
})
.listen(settings.httpPort);
if (settings.httpPort) {
http.createServer(middlewares.mock.requestHandler)
.on('error', function onError(err) {
console.log('API Mock Server (HTTP) ERROR:');
console.error(err.stack);
process.exit(-102);
})
.on('listening', function onListening() {
console.log('API Mock Server (HTTP) listening on port', settings.httpPort);
})
.on('connection', function onConnection(socket) {
socket.setTimeout(context.timeout);
})
.listen(settings.httpPort);
}

if (settings.httpsPort) {
var https = require('https')
, fs = require('fs')
;

// Read private key and certificate to set up an SSL server
var privateKey = fs.readFileSync(settings.httpsKeyPath, 'utf8')
, certificate = fs.readFileSync(settings.httpsCertPath, 'utf8');
var optsHttps = {
key: fs.readFileSync(settings.httpsKeyPath, 'utf8'),
cert: fs.readFileSync(settings.httpsCertPath, 'utf8')
};

https.createServer({ key: privateKey, cert: certificate }, middlewares.mock.requestHandler)
https.createServer(optsHttps, middlewares.mock.requestHandler)
.on('error', function onError(err) {
console.log('API Mock Server (HTTPS) ERROR:');
console.error(err.stack);
Expand All @@ -136,4 +138,29 @@ module.exports = function apiMockServer(settings) {
.listen(settings.httpsPort);
}

if (settings.twowaysslPort) {
// Read private key, certificate and authority certificate to set up a 2waySSL server
var optsTwowayssl = {
key: fs.readFileSync(settings.twowaysslKeyPath, 'utf8'),
cert: fs.readFileSync(settings.twowaysslCertPath, 'utf8'),
ca: fs.readFileSync(settings.twowaysslCAPath, 'utf8'),
requestCert: true,
rejectUnauthorized: true
};

https.createServer(optsTwowayssl, middlewares.mock.requestHandler)
.on('error', function onError(err) {
console.log('API Mock Server (2waySSL) ERROR:');
console.error(err.stack);
process.exit(-103);
})
.on('listening', function onListening() {
console.log('API Mock Server (2waySSL) listening on port', settings.twowaysslPort);
})
.on('connection', function onConnection(socket) {
socket.setTimeout(context.timeout);
})
.listen(settings.twowaysslPort);
}

};
12 changes: 9 additions & 3 deletions lib/chai-plugins/unica-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ chai.use(function(_chai, utils) {
Assertion.addProperty('wellFormedJsonApiResponse', function assertWellFormedJsonApiResponse() {
var negated = utils.flag(this, 'negate');
var res = this._obj;

new Assertion(res).to.have.httpHeaders('content-type');

var actualContentType = res.headers['content-type'].split(';')[0];
var expectedContentType = 'application/json';

this.assert(
actualContentType === expectedContentType,
'expected Content-Type header to be #{exp} but is #{act}',
Expand Down Expand Up @@ -73,9 +75,11 @@ chai.use(function(_chai, utils) {
Assertion.addProperty('wellFormedXmlApiResponse', function assertWellFormedXmlApiResponse() {
var negated = utils.flag(this, 'negate');
var res = this._obj;

new Assertion(res).to.have.httpHeaders('content-type');

var actualContentType = res.headers['content-type'].split(';')[0];
var expectedContentTypes = [ 'application/xml', 'text/xml' ];

this.assert(
expectedContentTypes.indexOf(actualContentType) !== -1,
'expected Content-Type header to be one of #{exp} but is #{act}',
Expand Down Expand Up @@ -104,9 +108,11 @@ chai.use(function(_chai, utils) {
Assertion.addProperty('wellFormedSoap11ApiResponse', function assertWellFormedSoap11ApiResponse() {
var negated = utils.flag(this, 'negate');
var res = this._obj;

new Assertion(res).to.have.httpHeaders('content-type');

var actualContentType = res.headers['content-type'].split(';')[0];
var expectedContentType = 'text/xml';

this.assert(
actualContentType === expectedContentType,
'expected Content-Type header to be #{exp} but is #{act}',
Expand Down
1 change: 1 addition & 0 deletions lib/collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ var request = require('request')
var ApiCollectionsGroup = function ApiCollectionsGroup(config) {
config = config || {};
config.headers = config.headers || {};
config.followRedirect = config.followRedirect || false;

/**
* This class models an specific collection, once customized to behave
Expand Down
4 changes: 2 additions & 2 deletions lib/mocha-gherkin/reporters/gherkin.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ var styles = {
variantPending: clc.green.italic,
stepLabel: clc.yellow,
stepText: clc.black,
stepLabelPending: clc.yellow.strike,
stepTextPending: clc.whiteBright.strike,
stepLabelPending: clc.yellow.italic,
stepTextPending: clc.black.italic,
stepLabelFailed: clc.red,
stepTextFailed: clc.red,
hookFailed: clc.red,
Expand Down
Loading

0 comments on commit 67c5df6

Please sign in to comment.