Set outgoing webhook asynchrone

This commit is contained in:
nioc 2019-11-23 19:18:35 +01:00
parent c0a75ad1f3
commit 003ab013d3
3 changed files with 83 additions and 74 deletions

View file

@ -9,14 +9,14 @@
* @license AGPL-3.0+ * @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) let webhook = config.getOutgoingWebhook(code)
if (!webhook) { if (!webhook) {
logger.warn(`There is no webhook with code "${code}"`) logger.warn(`There is no webhook with code "${code}"`)
callback(new Error(`There is no webhook with code "${code}"`), null, null) throw new Error(`There is no webhook with code "${code}"`)
return
} }
const request = require('request') const { promisify } = require('util')
const request = promisify(require('request'))
// request.debug = true // request.debug = true
let options = { let options = {
method: 'POST', method: 'POST',
@ -68,24 +68,20 @@ module.exports = (logger, config, xmpp, user, destination, message, type, code,
break break
} }
logger.trace('Outgoing webhook options:', options) logger.trace('Outgoing webhook options:', options)
request(options, function (error, response, body) { try {
logger.debug('statusCode:', response && response.statusCode) const { statusCode, body } = await request(options)
if (error) { if (statusCode === 200) {
logger.error('HTTP error:', error)
callback(new Error('HTTP error:', error), null, null)
return
}
if (response.statusCode === 200) {
logger.trace('Response:', body) logger.trace('Response:', body)
if (body && typeof (body) === 'object' && 'reply' in body === true) { if (body && typeof (body) === 'object' && 'reply' in body === true) {
logger.debug(`There is a reply to send back in chat ${destination}: ${body.reply}`) logger.debug(`There is a reply to send back in chat ${destination}: ${body.reply}`)
xmpp.send(destination, body.reply, type) xmpp.send(destination, body.reply, type)
callback(null, `Message sent. There is a reply to send back in chat ${destination}: ${body.reply}`, null) return `Message sent. There is a reply to send back in chat ${destination}: ${body.reply}`
return
} }
callback(null, 'Message sent', null) return 'Message sent'
return
} }
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
}
} }

View file

@ -86,7 +86,7 @@
], ],
"outgoingWebhooks": [ "outgoingWebhooks": [
{ {
"code": "w1", "code": "basic-json-reply",
"url": "https://domain.ltd:port/path/resource", "url": "https://domain.ltd:port/path/resource",
"strictSSL": true, "strictSSL": true,
"contentType": "application/json", "contentType": "application/json",
@ -96,7 +96,7 @@
"bearer": null "bearer": null
}, },
{ {
"code": "w2", "code": "bearer-form",
"url": "https://domain.ltd:port/path/resource", "url": "https://domain.ltd:port/path/resource",
"strictSSL": true, "strictSSL": true,
"contentType": "application/x-www-form-urlencoded", "contentType": "application/x-www-form-urlencoded",
@ -106,7 +106,7 @@
"bearer": "abcdefgh" "bearer": "abcdefgh"
}, },
{ {
"code": "w3", "code": "protected",
"url": "https://domain.ltd:port/path/protectedresource", "url": "https://domain.ltd:port/path/protectedresource",
"strictSSL": true, "strictSSL": true,
"contentType": "application/json", "contentType": "application/json",
@ -116,8 +116,8 @@
"bearer": null "bearer": null
}, },
{ {
"code": "w4", "code": "request-error",
"url": "https://domain.ltd:port/path/errorresource", "url": "https://domain.ltd:port/path/request-error",
"strictSSL": true, "strictSSL": true,
"contentType": "application/json", "contentType": "application/json",
"authMethod": null, "authMethod": null,

View file

@ -6,7 +6,6 @@ const sinon = require('sinon')
const should = require('chai').should() const should = require('chai').should()
const nock = require('nock') const nock = require('nock')
const Outgoing = require('./../lib/outgoing') const Outgoing = require('./../lib/outgoing')
require('chai').should()
describe('Outgoing webhook component', () => { describe('Outgoing webhook component', () => {
let logger, config, xmppSendStub, xmpp, scope, scopeUnauthorized, scopeWithError, reqSpy let logger, config, xmppSendStub, xmpp, scope, scopeUnauthorized, scopeWithError, reqSpy
@ -40,82 +39,96 @@ describe('Outgoing webhook component', () => {
.post('/path/resource') .post('/path/resource')
.reply(200, { reply: 'This is a reply' }) .reply(200, { reply: 'This is a reply' })
scope.on('request', reqSpy) scope.on('request', reqSpy)
scopeUnauthorized = nock('https://domain.ltd:port') scopeUnauthorized = nock('https://domain.ltd:port')
.post('/path/protectedresource') .post('/path/protectedresource')
.reply(401, {}) .reply(401, {})
scopeUnauthorized.on('request', reqSpy) scopeUnauthorized.on('request', reqSpy)
scopeWithError = nock('https://domain.ltd:port') scopeWithError = nock('https://domain.ltd:port')
.post('/path/errorresource') .post('/path/request-error')
.replyWithError('') .replyWithError('error in request')
scopeWithError.on('request', reqSpy) scopeWithError.on('request', reqSpy)
done() done()
}) })
describe('Unkwnow outgoing webhook', () => { describe('Unkwnow outgoing webhook', () => {
it('Should not execute request', (done) => { it('Should throw an exception and not execute request', async () => {
Outgoing(logger, config, xmpp, 'user', 'destination', 'message', 'type', 'code', (error, response, body) => { try {
should.not.equal(error, null) await Outgoing(logger, config, xmpp, 'user', 'destination', 'message', 'type', 'unknownCode')
sinon.assert.notCalled(reqSpy) should.fail(0, 1, 'Exception not thrown')
done() } 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', () => { 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) => { it('Should send basic authentication and JSON content-type in header and send an XMPP message', async () => {
Outgoing(logger, config, xmpp, 'user', 'destination', 'This a first message', 'type', 'w1', (error, response, body) => { let result
should.equal(error, null) try {
sinon.assert.calledOnce(reqSpy) result = await Outgoing(logger, config, xmpp, 'user', 'destination', 'This a first message', 'type', 'basic-json-reply')
const req = reqSpy.args[0][0] } catch (error) {
const bodyReq = JSON.parse(reqSpy.args[0][2]) should.fail(0, 1, 'Exception is thrown')
req.headers.authorization.should.equal('Basic dXNlcjM6M3Bhc3M=') }
req.headers['content-type'].should.equal('application/json') result.should.equal('Message sent. There is a reply to send back in chat destination: This is a reply')
bodyReq.from.should.equal('user') sinon.assert.calledOnce(reqSpy)
bodyReq.channel.should.equal('destination') const req = reqSpy.args[0][0]
bodyReq.message.should.equal('This a first message') const bodyReq = JSON.parse(reqSpy.args[0][2])
sinon.assert.calledOnce(xmppSendStub) req.headers.authorization.should.equal('Basic dXNlcjM6M3Bhc3M=')
const xmppSendArgs = xmppSendStub.args[0] req.headers['content-type'].should.equal('application/json')
xmppSendArgs[0].should.equal('destination') bodyReq.from.should.equal('user')
xmppSendArgs[1].should.equal('This is a reply') bodyReq.channel.should.equal('destination')
xmppSendArgs[2].should.equal('type') bodyReq.message.should.equal('This a first message')
done() 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', () => { describe('POST with bearer authorization and form-urlencoded content-type', () => {
it('Should send basic authentication in header', (done) => { it('Should send bearer authentication and form-urlencoded content-type in header', async () => {
Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'w2', (error, response, body) => { let result
should.equal(error, null) try {
sinon.assert.calledOnce(reqSpy) result = await Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'bearer-form')
const req = reqSpy.args[0][0] } catch (error) {
const bodyReq = decodeURIComponent(reqSpy.args[0][2]) should.fail(0, 1, 'Exception is thrown')
req.headers.authorization.should.equal('Bearer abcdefgh') }
req.headers['content-type'].should.equal('application/x-www-form-urlencoded') result.should.equal('Message sent')
bodyReq.should.equal('from=user&message=This a second message&channel=destination') sinon.assert.calledOnce(reqSpy)
done() 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', () => { describe('POST without authorization', () => {
it('Should not send authorization in header and handle 401', (done) => { it('Should not send authorization in header, handle 401 and throw an exception', async () => {
Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'w3', (error, response, body) => { try {
should.not.equal(error, null) await Outgoing(logger, config, xmpp, 'user', 'destination', 'message', 'type', 'protected')
sinon.assert.calledOnce(reqSpy) should.fail(0, 1, 'Exception not thrown')
done() } catch (error) {
}) error.message.should.equal('HTTP error: 401')
}
sinon.assert.calledOnce(reqSpy)
}) })
}) })
describe('POST with HTTP error', () => { describe('POST with HTTP error', () => {
it('Should handle error', (done) => { it('Should handle error and throw an exception', async () => {
Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'w4', (error, response, body) => { try {
should.not.equal(error, null) await Outgoing(logger, config, xmpp, 'user', 'destination', 'This a second message', 'type', 'request-error')
sinon.assert.calledOnce(reqSpy) should.fail(0, 1, 'Exception not thrown')
done() } catch (error) {
}) error.message.should.equal('error in request')
}
sinon.assert.calledOnce(reqSpy)
}) })
}) })
}) })