diff --git a/get.js b/get.js index 53140e9..ca17065 100644 --- a/get.js +++ b/get.js @@ -1,4 +1,5 @@ -var request = require('./request.js'); +'use strict'; +const request = require('./request.js'); exports.process = function (msg, conf, next) { request.get.call(this, msg, conf); diff --git a/package.json b/package.json index dcd1e4c..d2d46a1 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "elastic.io webhook component", "scripts": { - "test": "./node_modules/jasmine-node/bin/jasmine-node --test-dir spec" + "test": "mocha spec --recursive" }, "engines": { "node": "6.4.0" @@ -11,15 +11,18 @@ "author": "elastic.io GmbH", "license": "BSD-2-Clause", "dependencies": { - "debug": "^3.1.0", + "debug": "3.1.0", "elasticio-sailor-nodejs": "2.2.1", "elasticio-node": "0.0.8", "node-uuid": "1.4.8", "request": "2.83.x", - "q": "^1.5.1" + "q": "1.5.1" }, "devDependencies": { - "nock": "9.2.3", - "jasmine-node": "~1.14.5" + "eslint-plugin-mocha": "5.3.0", + "mocha": "6.1.4", + "nock": "10.0.6", + "sinon": "7.3.2", + "chai": "3.5.0" } } diff --git a/receive.js b/receive.js index 5677ad8..62b9165 100644 --- a/receive.js +++ b/receive.js @@ -1,16 +1,36 @@ "use strict"; -var Q = require("q"); +const Q = require("q"); exports.process = function (msg, conf) { - let msgId = msg.id; + const msgId = msg.id; console.log("Received new message with id", msgId); console.log(msg); - if (msg.query && msg.body) { - msg.body._query = msg.query; + if (msg.body) { + if (msg.query) { + msg.body._query = msg.query; + } + + if (msg.headers) { + msg.body._headers = msg.headers; + } + if (msg.method) { + msg.body.__method = msg.method; + } + + if (msg.url) { + msg.body._url = msg.url; + } + + if (msg.additionalUrlPath) { + msg.body._additionalUrlPath = msg.additionalUrlPath; + } + + console.log('Updated body', msg.body); } - var self = this; + + let self = this; Q() .then(emitData) @@ -31,4 +51,5 @@ exports.process = function (msg, conf) { console.log("Finished processing message:", msgId); self.emit('end'); } -}; \ No newline at end of file +}; + diff --git a/request.js b/request.js index aedae6a..b9c8292 100644 --- a/request.js +++ b/request.js @@ -1,16 +1,17 @@ -var request = require('request'); -var qs = require('querystring'); -var messages = require('elasticio-node').messages; -var debug = require('debug')('webhook:request'); +'use strict'; +const request = require('request'); +const qs = require('querystring'); +const messages = require('elasticio-node').messages; +const debug = require('debug')('webhook:request'); exports.putOrPost = function putOrPost(method, msg, conf) { 'use strict'; - var uri = conf.uri; - var body = msg.body; + const uri = conf.uri; + const body = msg.body; debug('Request body: %j', body); - var requestSettings = buildRequestSettings(method, uri, conf.secret); + let requestSettings = buildRequestSettings(method, uri, conf.secret); requestSettings.body = JSON.stringify(body); requestSettings.headers['Content-Type'] = 'application/json;charset=UTF-8'; @@ -19,7 +20,7 @@ exports.putOrPost = function putOrPost(method, msg, conf) { exports.get = function get(msg, conf, next) { 'use strict'; - var uri = conf.uri; + let uri = conf.uri; // Check if URI ends in ? If it doesn't add one. if (uri.charAt(uri.length - 1) !== '?') { @@ -28,13 +29,13 @@ exports.get = function get(msg, conf, next) { uri += qs.stringify(msg.body); - var requestSettings = buildRequestSettings('GET', uri, conf.secret); + const requestSettings = buildRequestSettings('GET', uri, conf.secret); request(requestSettings, callback.bind(this)); }; function buildRequestSettings(method, uri, secret) { 'use strict'; - var requestSettings = { + let requestSettings = { uri: uri, method: method, headers: {} @@ -56,7 +57,7 @@ function callback(err, response, body) { return; } - var sc = response.statusCode; + const sc = response.statusCode; if (sc >= 200 && sc <= 206) { this.emit('data', newMessage(response, body)); @@ -67,13 +68,13 @@ function callback(err, response, body) { } function newMessage(response, body) { - var headers = response.headers; + const headers = response.headers; - var contentType = headers['content-type']; + const contentType = headers['content-type']; - var msgBody = getJSONBody(contentType, body); + const msgBody = getJSONBody(contentType, body); - var msg = messages.newMessageWithBody(msgBody); + const msg = messages.newMessageWithBody(msgBody); msg.headers = headers; return msg; @@ -89,3 +90,4 @@ function getJSONBody(contentType, body) { responseBody : body } } + diff --git a/send.js b/send.js index 1607a35..29b5d0b 100644 --- a/send.js +++ b/send.js @@ -1,5 +1,6 @@ -var request = require('./request.js'); -var DEFAULT_METHOD = 'POST'; +'use strict'; +const request = require('./request.js'); +const DEFAULT_METHOD = 'POST'; exports.process = function(msg, conf) { request.putOrPost.call(this, conf.method || DEFAULT_METHOD, msg, conf); diff --git a/spec/webhook.spec.js b/spec/webhook.spec.js index fdbea29..5672050 100644 --- a/spec/webhook.spec.js +++ b/spec/webhook.spec.js @@ -1,326 +1,324 @@ -describe("Webhook", function () { - - var send = require('../send.js'); - var getMethod = require('../get.js'); - var receive = require('../receive.js'); - var nock = require('nock'); - - var self; - - beforeEach(function(){ - self = jasmine.createSpyObj('self', ['emit']); +/* eslint-env node, mocha */ +'use strict'; + +const expect = require('chai').expect; +const sinon = require('sinon'); +const nock = require('nock'); +const send = require('../send.js'); +const getMethod = require('../get.js'); +const receive = require('../receive.js'); + +describe("Test Webhook", () => { + afterEach(() => { + sinon.reset(); + }); + const webhookReturnObj = { message: 'ok', other: 'returned' }; + + it("PUT No Auth", async () => { + let nockObj = nock('http://www.example.com') + .put('/test', { + k1: "v1", + k2: "v2" + }) + .matchHeader('Content-Type', 'application/json;charset=UTF-8') + .reply(200, webhookReturnObj, { + 'Content-type': 'application/json;charset=UTF-8' }); - - describe("Outbound", function () { - var webhookReturnObj = {message: 'ok', other: 'returned'}; - - it('PUT No Auth', function () { - var nockObj = nock('http://www.example.com') - .put('/test', {k1:'v1', k2:'v2'}) - .matchHeader('Content-Type', 'application/json;charset=UTF-8') - .reply(200, webhookReturnObj, { - 'Content-type': 'application/json;charset=UTF-8' - }); - - runs(function() { - send.process.call(self, { - body:{k1:'v1', k2:'v2'} - }, { - uri: 'http://www.example.com/test', - method: 'PUT' - }); - }); - - waitsFor(function(){ - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function(){ - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('data'); - expect(self.emit.calls[0].args[1].body).toEqual(webhookReturnObj); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); - - it('PUT Auth', function () { - var nockObj = nock('http://www.example.com') - .put('/test', {k1:'v1', k2:'v2'}) - .matchHeader('Content-Type', 'application/json;charset=UTF-8') - .matchHeader('X-Api-Secret', 'theSecret') - .reply(200, webhookReturnObj, { - 'Content-type': 'application/json;charset=UTF-8' - }); - - runs(function() { - send.process.call(self, { - body:{k1:'v1', k2:'v2'} - }, { - uri: 'http://www.example.com/test', - secret:'theSecret', - method: 'PUT' - }); - }); - - waitsFor(function(){ - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function(){ - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('data'); - expect(self.emit.calls[0].args[1].body).toEqual(webhookReturnObj); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); - - it('POST and get text/html response', function () { - var nockObj = nock('http://www.example.com') - .post('/test', {k1:'v1', k2:'v2'}) - .matchHeader('Content-Type', 'application/json;charset=UTF-8') - .reply(200, webhookReturnObj, { - 'Content-type': 'text/html; charset=utf-8' - }); - - runs(function() { - send.process.call(self, { - body:{k1:'v1', k2:'v2'} - }, { - uri: 'http://www.example.com/test' - }); - }); - - waitsFor(function(){ - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function(){ - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('data'); - expect(self.emit.calls[0].args[1].body).toEqual({ - responseBody : '{"message":"ok","other":"returned"}' - }); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); - - it('POST Auth', function () { - var nockObj = nock('http://www.example.com') - .post('/test', {k1:'v1', k2:'v2'}) - .matchHeader('Content-Type', 'application/json;charset=UTF-8') - .matchHeader('X-Api-Secret', 'theSecret') - .reply(200, webhookReturnObj, { - 'Content-type': 'application/json;charset=UTF-8' - }); - - runs(function() { - send.process.call(self, { - body:{k1:'v1', k2:'v2'} - }, { - uri: 'http://www.example.com/test', - secret:'theSecret' - }); - }); - - waitsFor(function(){ - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function(){ - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('data'); - expect(self.emit.calls[0].args[1].body).toEqual(webhookReturnObj); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); - - it('POST Auth', function () { - var nockObj = nock('http://www.example.com') - .post('/test', {k1:'v1', k2:'v2'}) - .matchHeader('Content-Type', 'application/json;charset=UTF-8') - .matchHeader('X-Api-Secret', 'theSecret') - .reply(200, webhookReturnObj, { - 'Content-type': 'application/json;charset=UTF-8' - }); - - runs(function() { - send.process.call(self, { - body:{k1:'v1', k2:'v2'} - }, { - uri: 'http://www.example.com/test', - secret:'theSecret' - }); - }); - - waitsFor(function(){ - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function(){ - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('data'); - expect(self.emit.calls[0].args[1].body).toEqual(webhookReturnObj); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); - - it('GET No Auth No QMark', function () { - var nockObj = nock('http://www.example.com') - .get('/test?k1=v1&k2=v2') - .reply(200, webhookReturnObj); - - runs(function() { - getMethod.process.call(self, {body:{k1:'v1', k2:'v2'}}, {uri: 'http://www.example.com/test'}); - }); - - waitsFor(function(){ - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function(){ - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('data'); - expect(self.emit.calls[0].args[1].body).toEqual(webhookReturnObj); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); - - it('GET Auth No QMark', function () { - var nockObj = nock('http://www.example.com') - .get('/test?k1=v1&k2=v2') - .matchHeader('X-Api-Secret', 'theSecret') - .reply(200, webhookReturnObj); - - runs(function() { - getMethod.process.call(self, {body:{k1:'v1', k2:'v2'}}, {uri: 'http://www.example.com/test', secret:'theSecret'}); - }); - - waitsFor(function(){ - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function(){ - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('data'); - expect(self.emit.calls[0].args[1].body).toEqual(webhookReturnObj); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); - - it('GET No Auth QMark', function () { - var nockObj = nock('http://www.example.com') - .get('/test?k1=v1&k2=v2') - .reply(200, webhookReturnObj); - - runs(function() { - getMethod.process.call(self, {body:{k1:'v1', k2:'v2'}}, {uri: 'http://www.example.com/test?'}); - }); - - waitsFor(function(){ - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function(){ - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('data'); - expect(self.emit.calls[0].args[1].body).toEqual(webhookReturnObj); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); - - it('GET Auth QMark', function () { - var nockObj = nock('http://www.example.com') - .get('/test?k1=v1&k2=v2') - .matchHeader('X-Api-Secret', 'theSecret') - .reply(200, webhookReturnObj); - - runs(function() { - getMethod.process.call(self, {body:{k1:'v1', k2:'v2'}}, {uri: 'http://www.example.com/test?', secret:'theSecret'}); - }); - - waitsFor(function(){ - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function(){ - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('data'); - expect(self.emit.calls[0].args[1].body).toEqual(webhookReturnObj); - expect(self.emit.calls[1].args).toEqual(['end']); - }); + let self; + await new Promise((resolve, reject) => { + const emitter = { + emit: (name, value) => { + if ('end' === name) { + resolve(); + } + } + }; + self = sinon.spy(emitter, 'emit'); + send.process.call( + { emit: self }, { + body: { + k1: 'v1', + k2: 'v2' + } + }, { + uri: 'http://www.example.com/test', + method: 'PUT' + }); + } + ); + expect(nockObj.isDone()); + expect(self.calledTwice).to.be.true; + expect(self.args[0][1].body).to.eql(webhookReturnObj); + expect(self.args[1][0]).to.eql('end'); + }); + + it('PUT Auth', async() => { + let nockObj = nock('http://www.example.com') + .put('/test', { + k1: "v1", + k2: "v2" + }) + .matchHeader('Content-Type', 'application/json;charset=UTF-8') + .matchHeader('X-Api-Secret', 'theSecret') + .reply(200, webhookReturnObj, { + 'Content-type': 'application/json;charset=UTF-8' + }); + + let self; + await new Promise((resolve, reject) => { + const emitter = { + emit: (name, value) => { + if ('end' === name) { + resolve(); + } + } + }; + self = sinon.spy(emitter, 'emit'); + send.process.call( + { emit: self }, { + body: { + k1: 'v1', + k2: 'v2' + } + }, { + uri: 'http://www.example.com/test', + secret:'theSecret', + method: 'PUT' + }); + } + ); + expect(nockObj.isDone()); + expect(self.calledTwice).to.be.true; + expect(self.args[0][1].body).to.eql(webhookReturnObj); + expect(self.args[1][0]).to.eql('end'); + }); + + it('POST and get text/html response', async() => { + let nockObj = nock('http://www.example.com') + .post('/test', { + k1: "v1", + k2: "v2" + }) + .matchHeader('Content-Type', 'application/json;charset=UTF-8') + .reply(200, webhookReturnObj, { + 'Content-type': 'text/html; charset=utf-8' + }); + + let self; + await new Promise((resolve, reject) => { + const emitter = { + emit: (name, value) => { + if ('end' === name) { + resolve(); + } + } + }; + self = sinon.spy(emitter, 'emit'); + send.process.call( + { emit: self }, { + body: { + k1: 'v1', + k2: 'v2' + } + }, { + uri: 'http://www.example.com/test', + }); + } + ); + expect(nockObj.isDone()); + expect(self.calledTwice).to.be.true; + expect(self.args[0][1].body).to.eql({ + responseBody : '{"message":"ok","other":"returned"}' }); + expect(self.args[1][0]).to.eql('end'); - it('404', function () { - var nockObj = nock('http://www.example.com').get('/test?k1=v1&k2=v2').matchHeader('X-Api-Secret', 'theSecret').reply(404); + }); - runs(function () { - getMethod.process.call(self, {body: {k1: 'v1', k2: 'v2'}}, {uri: 'http://www.example.com/test?', secret: 'theSecret'}); - }); + it('GET No Auth No QMark', async() => { + let nockObj = nock('http://www.example.com') + .get('/test?k1=v1&k2=v2') + .reply(200, webhookReturnObj); + + let self; + await new Promise((resolve, reject) => { + const emitter = { + emit: (name, value) => { + if ('end' === name) { + resolve(); + } + } + }; + self = sinon.spy(emitter, 'emit'); + getMethod.process.call( + { emit: self }, { + body: { + k1: 'v1', + k2: 'v2' + }, + headers: { + test: 'header' + }, + url: "\/hook\/5d25e4598370bfb1c7c4696a\/Something?abc=def" + }, { + uri: 'http://www.example.com/test', + }); + } + ); + expect(nockObj.isDone()); + expect(self.calledTwice).to.be.true; + expect(self.args[0][0]).to.eql('data'); + expect(self.args[0][1].body).to.eql(webhookReturnObj); + expect(self.args[1][0]).to.eql('end'); - waitsFor(function () { - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); + }); - runs(function () { - expect(nockObj.isDone()); - expect(self.emit.calls[0].args[0]).toEqual('error'); - expect(self.emit.calls[0].args[1].message).toEqual('Endpoint responds with 404'); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); + it('GET Auth QMark', async() => { + let nockObj = nock('http://www.example.com') + .get('/test?k1=v1&k2=v2') + .matchHeader('X-Api-Secret', 'theSecret') + .reply(200, webhookReturnObj); + + let self; + await new Promise((resolve, reject) => { + const emitter = { + emit: (name, value) => { + if ('end' === name) { + resolve(); + } + } + }; + self = sinon.spy(emitter, 'emit'); + getMethod.process.call( + { emit: self }, { + body: { + k1: 'v1', + k2: 'v2' + }, + headers: { + test: 'header' + }, + url: "\/hook\/5d25e4598370bfb1c7c4696a\/Something?abc=def" + }, { + uri: 'http://www.example.com/test', + secret:'theSecret' + }); + } + ); + expect(nockObj.isDone()); + expect(self.calledTwice).to.be.true; + expect(self.args[0][0]).to.eql('data'); + expect(self.args[0][1].body).to.eql(webhookReturnObj); + expect(self.args[1][0]).to.eql('end'); }); + it('404', async() => { + let nockObj = nock('http://www.example.com') + .get('/test?k1=v1&k2=v2') + .matchHeader('X-Api-Secret', 'theSecret') + .reply(404); + + let self; + await new Promise((resolve, reject) => { + const emitter = { + emit: (name, value) => { + if ('end' === name) { + resolve(); + } + } + }; + self = sinon.spy(emitter, 'emit'); + getMethod.process.call( + { emit: self }, { + body: { + k1: 'v1', + k2: 'v2' + }, + headers: { + test: 'header' + }, + url: "\/hook\/5d25e4598370bfb1c7c4696a\/Something?abc=def" + }, { + uri: 'http://www.example.com/test?', + secret: 'theSecret' + }); + } + ); + expect(nockObj.isDone()); + expect(self.calledTwice).to.be.true; + expect(self.args[0][0]).to.eql('error'); + expect(self.args[0][1].message).to.eql('Endpoint responds with 404'); + expect(self.args[1][0]).to.eql('end'); + }); - it('Inbound', function () { - var msg = { - body: { - foo: "bar" - } + it('Inbound', async() => { + const msg = { + id: "1", + body: { + k1: 'v1', + k2: 'v2' + }, + headers: { + test: 'header' + }, + url: "\/hook\/5d25e4598370bfb1c7c4696a\/Something?abc=def" }; - - runs(function () { - receive.process.call(self, msg, {}); - }); - - waitsFor(function () { - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function () { - expect(self.emit.calls[0].args[1]).toBeDefined(); - expect(self.emit.calls[0].args[1].body).toBeDefined(); - expect(self.emit.calls[0].args[1].body._query).toBeUndefined(); - expect(self.emit.calls[0].args).toEqual(['data', msg]); - expect(self.emit.calls[1].args).toEqual(['end']); - }); + let self; + await new Promise((resolve, reject) => { + const emitter = { + emit: (name, value) => { + if ('end' === name || 'error' === name) { + resolve(); + } + } + }; + self = sinon.spy(emitter, 'emit'); + receive.process.call( { emit: self }, msg, {}); + } + ); + expect(self.args[0][1]).to.be.not.udefined; + expect(self.args[0][1].body).to.be.not.udefined; + expect(self.args[0][1].body._query).to.be.not.udefined; + expect(self.args[0][1].body._url).to.eql("\/hook\/5d25e4598370bfb1c7c4696a\/Something?abc=def"); + expect(self.args[0]).to.eql(['data', msg]); + expect(self.args[1][0]).to.eql('end'); }); - it('Inbound with query', function () { - var msg = { + it('Inbound with query', async() => { + const msg = { + id: "1", body: { - foo: "bar" + k1: 'v1', + k2: 'v2' + }, + headers: { + test: 'header' }, + url: "\/hook\/5d25e4598370bfb1c7c4696a\/Something?abc=def", query : { baz: 'boo' } }; + let self; + await new Promise((resolve, reject) => { + const emitter = { + emit: (name, value) => { + if ('end' === name) { + resolve(); + } + } + }; + self = sinon.spy(emitter, 'emit'); + receive.process.call( { emit: self }, msg, {}); + } + ); + expect(self.args[0][1]).to.be.not.udefined; + expect(self.args[0][1].body).to.be.not.udefined; + expect(self.args[0][1].body._query).to.eql({baz: 'boo'}); + expect(self.args[0][1].body._url).to.eql("\/hook\/5d25e4598370bfb1c7c4696a\/Something?abc=def"); + expect(self.args[0]).to.eql(['data', msg]); + expect(self.args[1][0]).to.eql('end'); + }) +}); - runs(function () { - receive.process.call(self, msg, {}); - }); - - waitsFor(function () { - return self.emit.calls.length === 2; - }, 'Timed Out', 1000); - - runs(function () { - expect(self.emit.calls[0].args[1]).toBeDefined(); - expect(self.emit.calls[0].args[1].body).toBeDefined(); - expect(self.emit.calls[0].args[1].body._query).toEqual({baz: 'boo'}); - expect(self.emit.calls[0].args).toEqual(['data', msg]); - expect(self.emit.calls[1].args).toEqual(['end']); - }); - }); -}); \ No newline at end of file