diff --git a/CONTRIBUTERS.md b/CONTRIBUTERS.md index 8069f6e..033dc76 100644 --- a/CONTRIBUTERS.md +++ b/CONTRIBUTERS.md @@ -24,6 +24,8 @@ [JJ4TH](https://github.com/jj4th) +[Aparna Sathyanarayan](https://github.com/aparna-sath) + # Special Thanks Alexander Zagniotov diff --git a/README.md b/README.md index 7f51401..a314585 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Some systems require you to `sudo` before running services on certain ports (lik ``` stubby [-a <port>] [-c <file>] [-d <file>] [-h] [-k <file>] [-l <hostname>] [-p <file>] [-q] - [-s <port>] [-t <port>] [-v] [-w] + [-s <port>] [-t <port>] [-v] [-w] [-q] [-r] -a, --admin <port> Port for admin portal. Defaults to 8889. -c, --cert <file> Certificate file. Use with --key. @@ -67,6 +67,7 @@ stubby [-a <port>] [-c <file>] [-d <file>] [-h] [-k <file>] [-l <hostname>] [-p -t, --tls <port> Port for https stubs portal. Defaults to 7443. -v, --version Prints stubby's version number. -w, --watch Auto-reload data file when edits are made. +-r, --dittoResponse Ditto the response back to the client, without any modifications. ``` When used from the command-line, `stubby` responds to the `SIGHUP` signal to reload its configuration. @@ -671,6 +672,7 @@ What can I do with it, you ask? Read on! * `quiet`: defaults to `true`. Pass in `false` to have console output (if available) * `_httpsOptions`: additional options to pass to the [underlying tls server](http://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener). + * `dittoResponse`: tells stubby to send the response back without lower-casing the headers * `callback`: takes one parameter: the error message (if there is one), undefined otherwise #### start([callback]) diff --git a/src/console/cli.js b/src/console/cli.js index 8028e23..954fd06 100644 --- a/src/console/cli.js +++ b/src/console/cli.js @@ -69,6 +69,11 @@ var options = [{ name: 'watch', flag: 'w', description: 'Auto-reload data file when edits are made.' +}, { + name: 'dittoResponse', + flag: 'r', + default: false, + description: 'Maintain casing for response headers.' }]; function help (go) { diff --git a/src/main.js b/src/main.js index a946a67..d64ff05 100644 --- a/src/main.js +++ b/src/main.js @@ -99,6 +99,7 @@ Stubby.prototype.start = function (o, cb) { if (errors) { return callback(errors); } if (options.datadir != null) { self.endpoints.datadir = options.datadir; } + if (options.dittoResponse != null) { self.endpoints.dittoResponse = options.dittoResponse; } self.endpoints.create(options.data, onEndpointLoaded); diff --git a/src/models/endpoint.js b/src/models/endpoint.js index 6eed102..10ab9e6 100644 --- a/src/models/endpoint.js +++ b/src/models/endpoint.js @@ -6,14 +6,15 @@ var http = require('http'); var q = require('querystring'); var out = require('../console/out'); -function Endpoint (endpoint, datadir) { +function Endpoint (endpoint, datadir, dittoResponse) { if (endpoint == null) { endpoint = {}; } if (datadir == null) { datadir = process.cwd(); } + if (dittoResponse == null) { dittoResponse = false; } Object.defineProperty(this, 'datadir', { value: datadir }); this.request = purifyRequest(endpoint.request); - this.response = purifyResponse(this, endpoint.response); + this.response = purifyResponse(this, endpoint.response, dittoResponse); this.hits = 0; } @@ -117,7 +118,7 @@ function purifyRequest (incoming) { outgoing = { url: incoming.url, method: incoming.method == null ? 'GET' : incoming.method, - headers: purifyHeaders(incoming.headers), + headers: purifyHeaders(incoming.headers, true), query: incoming.query, file: incoming.file, post: incoming.post, @@ -133,7 +134,7 @@ function purifyRequest (incoming) { return outgoing; } -function purifyResponse (me, incoming) { +function purifyResponse (me, incoming, dittoResponse) { var outgoing = []; if (incoming == null) { incoming = []; } @@ -145,7 +146,7 @@ function purifyResponse (me, incoming) { outgoing.push(record(me, response)); } else { outgoing.push(pruneUndefined({ - headers: purifyHeaders(response.headers), + headers: purifyHeaders(response.headers, !dittoResponse), status: parseInt(response.status, 10) || 200, latency: parseInt(response.latency, 10) || null, file: response.file, @@ -157,13 +158,17 @@ function purifyResponse (me, incoming) { return outgoing; } -function purifyHeaders (incoming) { +function purifyHeaders (incoming, shouldLowerCase) { var prop; var outgoing = {}; for (prop in incoming) { if (Object.prototype.hasOwnProperty.call(incoming, prop)) { - outgoing[prop.toLowerCase()] = incoming[prop]; + if (shouldLowerCase) { + outgoing[prop.toLowerCase()] = incoming[prop]; + } else { + outgoing[prop] = incoming[prop]; + } } } diff --git a/src/models/endpoints.js b/src/models/endpoints.js index fd23043..0201e59 100644 --- a/src/models/endpoints.js +++ b/src/models/endpoints.js @@ -15,6 +15,7 @@ function Endpoints (data, callback, datadir) { if (callback == null) { callback = noop; } if (datadir == null) { datadir = process.cwd(); } + this.dittoResponse = false; this.datadir = datadir; this.db = {}; this.lastId = 0; @@ -27,7 +28,7 @@ Endpoints.prototype.create = function (data, callback) { if (callback == null) { callback = noop; } function insert (item) { - item = new Endpoint(item, self.datadir); + item = new Endpoint(item, self.datadir, self.dittoResponse); item.id = ++self.lastId; self.db[item.id] = item; callback(null, clone(item)); diff --git a/test/cli.js b/test/cli.js index f96e084..980b955 100644 --- a/test/cli.js +++ b/test/cli.js @@ -231,6 +231,26 @@ describe('CLI', function () { }); }); + describe('-r, --dittoResponse', function () { + it('should return default if no flag provided', function () { + const expected = false; + const actual = sut.getArgs([]); + assert.strictEqual(actual.dittoResponse, expected); + }); + + it('should return supplied value when provided', function () { + const expected = true; + const actual = sut.getArgs(['-r', expected]); + assert.strictEqual(actual.dittoResponse, expected); + }); + + it('should return supplied value when provided with full flag', function () { + const expected = true; + const actual = sut.getArgs(['--dittoResponse', expected]); + assert.strictEqual(actual.dittoResponse, expected); + }); + }); + describe('getArgs', function () { it('should gather all arguments', function () { var actual; @@ -247,6 +267,7 @@ describe('CLI', function () { quiet: true, watch: filename, datadir: process.cwd(), + dittoResponse: false, help: undefined, // eslint-disable-line no-undefined version: (require('../package.json')).version }; diff --git a/test/endpoint.js b/test/endpoint.js index 2aac753..3e3b96c 100644 --- a/test/endpoint.js +++ b/test/endpoint.js @@ -269,6 +269,33 @@ describe('Endpoint', function () { assert.deepStrictEqual(actual.request.headers, expected.request); }); + it('should not lower case response headers properties if dittoResponse is true', function () { + var actual, expected; + this.data.request = { + headers: { + 'Content-Type': 'application/json' + } + }; + this.data.response = { + headers: { + 'Content-Type': 'application/json' + } + }; + expected = { + request: { + 'content-type': 'application/json' + }, + response: { + 'Content-Type': 'application/json' + } + }; + + actual = new Endpoint(this.data, null, true); + + assert.deepStrictEqual(actual.response[0].headers, expected.response); + assert.deepStrictEqual(actual.request.headers, expected.request); + }); + it('should define multiple headers with same name', function () { var actual, expected; this.data.request = {