diff --git a/README.md b/README.md index 520ba88..43f26bb 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ dafuq allows you to create an api that executes files on the os command line (vi * **debug** (optional): Show debug info. If true, `console.log` will be used as loggin function. If a function it will used as loggin function instead of the default . Defaults to `false`. * **bearer** (optional): Add bearer token authorization method to the api. The acces token is provided as the value of this config. Defaults to '' * **timeout** (optional): Time to wait before killing an spawned command. Defaults to `0` which means infinite. +* **middlewares** (optional): Array of middlewares that will be executed after the command has run but before sending the api respnse. The response object has a `dafuq` property containing `result` and `type` properties with the result of the execution. ### Example diff --git a/package.json b/package.json index 8a60fdc..cdb3412 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "mocha": "^3.0.2", "nyc": "^8.1.0", "should": "^11.1.0", + "sinon": "^1.17.5", "supertest": "^2.0.0" }, "repository": "Upplication/node-dafuq", diff --git a/src/index.js b/src/index.js index cfbedac..40dc77b 100644 --- a/src/index.js +++ b/src/index.js @@ -10,8 +10,9 @@ const fs = require('fs') const IS_TEST = process.env['NODE_ENV'] === 'test' -const RESULT_PROPERTY = 'dafuq_result' -const RESULT_TYPE_PROPERTY = 'dafuq_type' +const RESPONSE_RESULT_CONTAINER = 'dafuq' +const RESPONSE_RESULT_TYPE = 'type' +const RESPONSE_RESULT = 'result' /** * @typedef DafuqPath @@ -249,10 +250,13 @@ function accessMiddleware(token) { */ function resultMiddleware() { return (req, res, next) => { - if (res[RESULT_TYPE_PROPERTY] === RESULT_TYPE_OBJECT) - res.type('json').json(res[RESULT_PROPERTY]) - else if (res[RESULT_TYPE_PROPERTY] === RESULT_TYPE_FILE) - res.download(res[RESULT_PROPERTY]) + const result = res[RESPONSE_RESULT_CONTAINER][RESPONSE_RESULT] + , type = res[RESPONSE_RESULT_CONTAINER][RESPONSE_RESULT_TYPE] + + if (type === RESULT_TYPE_OBJECT) + res.type('json').json(result) + else if (type === RESULT_TYPE_FILE) + res.download(result) else // This shouldn't happen, but just in case res.status(500).send() } @@ -269,7 +273,8 @@ export default function dafuq(config) { shebang: '', debug: false, brearer: '', - timeout: 0 + timeout: 0, + middlewares: [] }, config) // Options validation @@ -290,6 +295,10 @@ export default function dafuq(config) { if (opts.timeout && (typeof opts.timeout !== 'number')) throw new TypeError('timeout must be a number') + // If middlewares provided, but not valid + if (opts.middlewares !== undefined && !Array.isArray(opts.middlewares)) + throw new TypeError('middlewares must be a an array') + if (opts.debug !== undefined) { if (opts.debug === true) opts.debug = IS_TEST ? (() => {}) : console.log @@ -334,6 +343,9 @@ export default function dafuq(config) { */ function executionMiddleware(file) { return (req, res, next) => { + // Initialize the dafuq result container + Object.defineProperty(res, RESPONSE_RESULT_CONTAINER, { value: {} }) + // Build the base command let cmd = file if (opts.shebang) @@ -349,8 +361,14 @@ export default function dafuq(config) { opts.debug(`$ ${cmd}`) execCommand(cmd, opts.timeout, (result, type) => { - Object.defineProperty(res, RESULT_PROPERTY, { value: result }) - Object.defineProperty(res, RESULT_TYPE_PROPERTY, { value: type }) + Object.defineProperty(res[RESPONSE_RESULT_CONTAINER], RESPONSE_RESULT_TYPE, { + enumerable: true, + value: type + }) + Object.defineProperty(res[RESPONSE_RESULT_CONTAINER], RESPONSE_RESULT, { + enumerable: true, + value: result + }) next() }) } @@ -373,6 +391,8 @@ export default function dafuq(config) { middlewares.push(upload.any()) middlewares.push(executionMiddleware(file.absolute)) + // Allow clients to do something with the responses before sending it + middlewares.push(...opts.middlewares) middlewares.push(resultMiddleware()) opts.debug(`Adding ${ method } ${ url }`) diff --git a/test/test.js b/test/test.js index a335c2a..0f0dcf7 100644 --- a/test/test.js +++ b/test/test.js @@ -1,7 +1,8 @@ require('should') const p = require('path') const fs = require('fs') -const request = require('supertest'); +const request = require('supertest') +const sinon = require('sinon') const dafuq = require(process.env['DAFUQ_COVERAGE'] ? '../src-cov' : '../src') describe('Constructor', function() { @@ -124,22 +125,78 @@ describe('Constructor', function() { build({ path: './commands', debug: '' - }).should.throw(); + }).should.throw(/debug/); build({ path: './commands', debug: 'string' - }).should.throw(); + }).should.throw(/debug/); build({ path: './commands', debug: 123 - }).should.throw(); + }).should.throw(/debug/); build({ path: './commands', debug: {} - }).should.throw(); + }).should.throw(/debug/); build({ path: './commands', debug: [] + }).should.throw(/debug/); + }) + + it('should throw if middlewares is not an array of functions', function() { + build({ + path: './commands', + middlewares: [] + }).should.not.throw(); + build({ + path: './commands', + middlewares: true + }).should.throw(/middlewares/); + build({ + path: './commands', + middlewares: function() {} + }).should.throw(/middlewares/); + build({ + path: './commands', + middlewares: '' + }).should.throw(/middlewares/); + build({ + path: './commands', + middlewares: 'string' + }).should.throw(/middlewares/); + build({ + path: './commands', + middlewares: 123 + }).should.throw(/middlewares/); + build({ + path: './commands', + middlewares: {} + }).should.throw(/middlewares/); + // Arrays of + build({ + path: './commands', + middlewares: [ () => {} ] + }).should.not.throw(); + build({ + path: './commands', + middlewares: [ true ] + }).should.throw(); + build({ + path: './commands', + middlewares: [ '' ] + }).should.throw(); + build({ + path: './commands', + middlewares: [ 'string' ] + }).should.throw(); + build({ + path: './commands', + middlewares: [ 123 ] + }).should.throw(); + build({ + path: './commands', + middlewares: [ {} ] }).should.throw(); }) }) @@ -317,6 +374,65 @@ describe('Invoking a file', () => { .end(done) }) }) + + describe('specifing middlewares', () => { + let app, spy1, spy2; + + before(() => { + spy1 = sinon.stub().callsArg(2) + spy2 = sinon.stub().callsArg(2) + app = dafuq({ + path: './commands', + middlewares: [ spy1, spy2 ] + }); + }) + + beforeEach(() => { + spy1.reset() + spy2.reset() + }) + + it('should call each middleware once in order of definition', (done) => { + request(app) + .get('/hello') + .expect(200) + .expect('Content-Type', /json/) + .expect(() => { + spy1.calledOnce.should.be.true() + spy2.calledOnce.should.be.true() + spy1.calledBefore(spy2) + }) + .end(done) + }) + + it('should pass each middleware the result type of execution', (done) => { + request(app) + .get('/hello') + .expect(200) + .expect('Content-Type', /json/) + .expect(response => { + const [ req, res, next ] = spy1.args[0] + res.should.have.property('dafuq') + res.dafuq.should.have.property('result') + response.body.should.be.eql(res.dafuq.result) + }) + .end(done) + }) + + it('should pass each middleware the result of execution', (done) => { + request(app) + .get('/hello') + .expect(200) + .expect('Content-Type', /json/) + .expect(response => { + const [ req, res, next ] = spy1.args[0] + res.should.have.property('dafuq') + res.dafuq.should.have.property('type') + }) + .end(done) + }) + }) + }) describe('Arguments', () => {