diff --git a/lib/outgoing/index.js b/lib/outgoing/index.js index 814468b..c3b57ce 100644 --- a/lib/outgoing/index.js +++ b/lib/outgoing/index.js @@ -9,14 +9,14 @@ * @license AGPL-3.0+ */ -module.exports = (logger, config, xmpp, user, destination, message, type, code, callback = () => {}) => { +module.exports = async (logger, config, xmpp, user, destination, message, type, code) => { let webhook = config.getOutgoingWebhook(code) if (!webhook) { logger.warn(`There is no webhook with code "${code}"`) - callback(new Error(`There is no webhook with code "${code}"`), null, null) - return + throw new Error(`There is no webhook with code "${code}"`) } - const request = require('request') + const { promisify } = require('util') + const request = promisify(require('request')) // request.debug = true let options = { method: 'POST', @@ -68,24 +68,20 @@ module.exports = (logger, config, xmpp, user, destination, message, type, code, break } logger.trace('Outgoing webhook options:', options) - request(options, function (error, response, body) { - logger.debug('statusCode:', response && response.statusCode) - if (error) { - logger.error('HTTP error:', error) - callback(new Error('HTTP error:', error), null, null) - return - } - if (response.statusCode === 200) { + try { + const { statusCode, body } = await request(options) + if (statusCode === 200) { logger.trace('Response:', body) if (body && typeof (body) === 'object' && 'reply' in body === true) { logger.debug(`There is a reply to send back in chat ${destination}: ${body.reply}`) xmpp.send(destination, body.reply, type) - callback(null, `Message sent. There is a reply to send back in chat ${destination}: ${body.reply}`, null) - return + return `Message sent. There is a reply to send back in chat ${destination}: ${body.reply}` } - callback(null, 'Message sent', null) - return + return 'Message sent' } - callback(new Error('HTTP error:', response.statusCode), null, null) - }) + throw new Error(`HTTP error: ${statusCode}`) + } catch (error) { + logger.error(`Error during outgoing webhook request: ${error.message}`) + throw error + } } diff --git a/test/config.json b/test/config.json index e7c6cb3..1430465 100644 --- a/test/config.json +++ b/test/config.json @@ -86,7 +86,7 @@ ], "outgoingWebhooks": [ { - "code": "w1", + "code": "basic-json-reply", "url": "https://domain.ltd:port/path/resource", "strictSSL": true, "contentType": "application/json", @@ -96,7 +96,7 @@ "bearer": null }, { - "code": "w2", + "code": "bearer-form", "url": "https://domain.ltd:port/path/resource", "strictSSL": true, "contentType": "application/x-www-form-urlencoded", @@ -106,7 +106,7 @@ "bearer": "abcdefgh" }, { - "code": "w3", + "code": "protected", "url": "https://domain.ltd:port/path/protectedresource", "strictSSL": true, "contentType": "application/json", @@ -116,8 +116,8 @@ "bearer": null }, { - "code": "w4", - "url": "https://domain.ltd:port/path/errorresource", + "code": "request-error", + "url": "https://domain.ltd:port/path/request-error", "strictSSL": true, "contentType": "application/json", "authMethod": null, diff --git a/test/outgoing.js b/test/outgoing.js index ad06a46..f9a810e 100644 --- a/test/outgoing.js +++ b/test/outgoing.js @@ -6,7 +6,6 @@ const sinon = require('sinon') const should = require('chai').should() const nock = require('nock') const Outgoing = require('./../lib/outgoing') -require('chai').should() describe('Outgoing webhook component', () => { let logger, config, xmppSendStub, xmpp, scope, scopeUnauthorized, scopeWithError, reqSpy @@ -40,82 +39,96 @@ describe('Outgoing webhook component', () => { .post('/path/resource') .reply(200, { reply: 'This is a reply' }) scope.on('request', reqSpy) + scopeUnauthorized = nock('https://domain.ltd:port') .post('/path/protectedresource') .reply(401, {}) scopeUnauthorized.on('request', reqSpy) + scopeWithError = nock('https://domain.ltd:port') - .post('/path/errorresource') - .replyWithError('') + .post('/path/request-error') + .replyWithError('error in request') scopeWithError.on('request', reqSpy) done() }) describe('Unkwnow outgoing webhook', () => { - it('Should not execute request', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'message', 'type', 'code', (error, response, body) => { - should.not.equal(error, null) - sinon.assert.notCalled(reqSpy) - done() - }) + it('Should throw an exception and not execute request', async () => { + try { + await Outgoing(logger, config, xmpp, 'user', 'destination', 'message', 'type', 'unknownCode') + should.fail(0, 1, 'Exception not thrown') + } catch (error) { + error.message.should.equal('There is no webhook with code "unknownCode"') + } + sinon.assert.notCalled(reqSpy) }) }) describe('POST with basic authorization and JSON content-type and reply message to XMPP', () => { - it('Should send basic authentication and JSON content-type in header and send an XMPP message', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'This a first message', 'type', 'w1', (error, response, body) => { - should.equal(error, null) - sinon.assert.calledOnce(reqSpy) - const req = reqSpy.args[0][0] - const bodyReq = JSON.parse(reqSpy.args[0][2]) - req.headers.authorization.should.equal('Basic dXNlcjM6M3Bhc3M=') - req.headers['content-type'].should.equal('application/json') - bodyReq.from.should.equal('user') - bodyReq.channel.should.equal('destination') - bodyReq.message.should.equal('This a first message') - sinon.assert.calledOnce(xmppSendStub) - const xmppSendArgs = xmppSendStub.args[0] - xmppSendArgs[0].should.equal('destination') - xmppSendArgs[1].should.equal('This is a reply') - xmppSendArgs[2].should.equal('type') - done() - }) + it('Should send basic authentication and JSON content-type in header and send an XMPP message', async () => { + let result + try { + result = await Outgoing(logger, config, xmpp, 'user', 'destination', 'This a first message', 'type', 'basic-json-reply') + } catch (error) { + should.fail(0, 1, 'Exception is thrown') + } + result.should.equal('Message sent. There is a reply to send back in chat destination: This is a reply') + sinon.assert.calledOnce(reqSpy) + const req = reqSpy.args[0][0] + const bodyReq = JSON.parse(reqSpy.args[0][2]) + req.headers.authorization.should.equal('Basic dXNlcjM6M3Bhc3M=') + req.headers['content-type'].should.equal('application/json') + bodyReq.from.should.equal('user') + bodyReq.channel.should.equal('destination') + bodyReq.message.should.equal('This a first message') + sinon.assert.calledOnce(xmppSendStub) + const xmppSendArgs = xmppSendStub.args[0] + xmppSendArgs[0].should.equal('destination') + xmppSendArgs[1].should.equal('This is a reply') + xmppSendArgs[2].should.equal('type') }) }) - describe('POST with bearer authorization and JSON content-type', () => { - it('Should send basic authentication in header', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'w2', (error, response, body) => { - should.equal(error, null) - sinon.assert.calledOnce(reqSpy) - const req = reqSpy.args[0][0] - const bodyReq = decodeURIComponent(reqSpy.args[0][2]) - req.headers.authorization.should.equal('Bearer abcdefgh') - req.headers['content-type'].should.equal('application/x-www-form-urlencoded') - bodyReq.should.equal('from=user&message=This a second message&channel=destination') - done() - }) + describe('POST with bearer authorization and form-urlencoded content-type', () => { + it('Should send bearer authentication and form-urlencoded content-type in header', async () => { + let result + try { + result = await Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'bearer-form') + } catch (error) { + should.fail(0, 1, 'Exception is thrown') + } + result.should.equal('Message sent') + sinon.assert.calledOnce(reqSpy) + const req = reqSpy.args[0][0] + const bodyReq = decodeURIComponent(reqSpy.args[0][2]) + req.headers.authorization.should.equal('Bearer abcdefgh') + req.headers['content-type'].should.equal('application/x-www-form-urlencoded') + bodyReq.should.equal('from=user&message=This a second message&channel=destination') }) }) describe('POST without authorization', () => { - it('Should not send authorization in header and handle 401', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'w3', (error, response, body) => { - should.not.equal(error, null) - sinon.assert.calledOnce(reqSpy) - done() - }) + it('Should not send authorization in header, handle 401 and throw an exception', async () => { + try { + await Outgoing(logger, config, xmpp, 'user', 'destination', 'message', 'type', 'protected') + should.fail(0, 1, 'Exception not thrown') + } catch (error) { + error.message.should.equal('HTTP error: 401') + } + sinon.assert.calledOnce(reqSpy) }) }) describe('POST with HTTP error', () => { - it('Should handle error', (done) => { - Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'w4', (error, response, body) => { - should.not.equal(error, null) - sinon.assert.calledOnce(reqSpy) - done() - }) + it('Should handle error and throw an exception', async () => { + try { + await Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'request-error') + should.fail(0, 1, 'Exception not thrown') + } catch (error) { + error.message.should.equal('error in request') + } + sinon.assert.calledOnce(reqSpy) }) }) })