diff --git a/test/xmpp.js b/test/xmpp.js index bb8dd08..7b9b73c 100644 --- a/test/xmpp.js +++ b/test/xmpp.js @@ -2,16 +2,16 @@ process.env.NODE_ENV = 'production' const sinon = require('sinon') +require('chai').should() const EventEmitter = require('events').EventEmitter const mock = require('mock-require') const xml = require('@xmpp/xml') const jid = require('@xmpp/jid') describe('XMPP component', () => { - const simpleXmppEvents = new EventEmitter() - let logger, config, outgoingStub, xmppSendStub, xmppCloseStub + let logger, config, outgoingStub, outgoingStubRejected, xmppSendStub, xmppCloseStub, simpleXmppEvents, xmpp - before('Setup', (done) => { + before('Setup logger and config', (done) => { // create default logger logger = require('./../lib/logger')() @@ -21,6 +21,25 @@ describe('XMPP component', () => { // update logger with configuration logger.updateConfig(config.logger) + // mock logger trace + sinon.stub(logger, 'trace') + + done() + }) + + after('Remove mocks', (done) => { + mock.stopAll() + logger.trace.restore() + done() + }) + + beforeEach('Setup XMPP client with stub', function () { + simpleXmppEvents = new EventEmitter() + + // mock outgoing module + outgoingStub = sinon.stub().resolves() + mock('./../lib/outgoing', outgoingStub) + // mock @xmpp/client module xmppSendStub = sinon.stub().resolves() xmppCloseStub = sinon.stub().resolves() @@ -38,32 +57,21 @@ describe('XMPP component', () => { jid: require('@xmpp/jid') }) - // mock outgoing - outgoingStub = sinon.stub() - mock('./../lib/outgoing', outgoingStub) - - // mock logger trace - sinon.stub(logger, 'trace') - - done() - }) - - after('Remove mocks', (done) => { - mock.stopAll() - logger.trace.restore() - done() - }) - - beforeEach('Reset outgoing and xmpp stub history', function () { + // reset stubs history outgoingStub.resetHistory() xmppSendStub.resetHistory() xmppCloseStub.resetHistory() + + // call module and emit online event + xmpp = require('./../lib/xmpp')(logger, config) + simpleXmppEvents.emit('online', jid('bot@domain-xmpp.ltd/resource')) }) describe('Connect to XMPP server', () => { + beforeEach(async () => { + await simpleXmppEvents.emit('status', 'connecting') + }) it('Should connect to XMPP server and join rooms when application start', (done) => { - require('./../lib/xmpp')(logger, config) - simpleXmppEvents.emit('online', jid('bot@domain-xmpp.ltd/resource')) sinon.assert.called(xmppSendStub) // 1 "send" call for presence and n "send" calls for joining rooms let roomsLength = config.xmpp.rooms.length @@ -87,42 +95,46 @@ describe('XMPP component', () => { done() }) it('Should trace connection status', (done) => { - simpleXmppEvents.emit('status', 'connecting') sinon.assert.calledWith(logger.trace, 'Status changed to connecting') done() }) }) describe('Bot receive a presence stanza from someone', () => { - it('Should not trigger outgoing webhook', (done) => { - simpleXmppEvents.emit('stanza', xml( + beforeEach(async () => { + await simpleXmppEvents.emit('stanza', xml( 'presence', { from: 'someone@domain-xmpp.ltd', to: 'bot@domain-xmpp.ltd' } )) + }) + it('Should not trigger outgoing webhook', (done) => { sinon.assert.notCalled(outgoingStub) done() }) }) describe('Bot receive an empty message from someone', () => { - it('Should not trigger outgoing webhook', (done) => { - simpleXmppEvents.emit('stanza', xml( + beforeEach(async () => { + await simpleXmppEvents.emit('stanza', xml( 'message', { from: 'someone@domain-xmpp.ltd', to: 'bot@domain-xmpp.ltd', type: 'chat' } )) + }) + it('Should not trigger outgoing webhook', (done) => { sinon.assert.notCalled(outgoingStub) done() }) }) describe('Bot receive a message from someone', () => { - it('Should trigger outgoing webhook with valid arguments and send a message delivery receipt', (done) => { - simpleXmppEvents.emit('stanza', xml( + beforeEach(async () => { + xmppSendStub.resetHistory() + await simpleXmppEvents.emit('stanza', xml( 'message', { from: 'someone@domain-xmpp.ltd', to: 'bot@domain-xmpp.ltd', @@ -138,6 +150,8 @@ describe('XMPP component', () => { xmlns: 'urn:xmpp:receipts' }) )) + }) + it('Should trigger outgoing webhook with valid arguments and send a message delivery receipt', (done) => { sinon.assert.calledOnce(outgoingStub) const args = outgoingStub.args[0] args.should.have.length(8) @@ -163,9 +177,55 @@ describe('XMPP component', () => { }) }) + describe('Bot receive a message from someone but webhook call failed', () => { + beforeEach(async () => { + simpleXmppEvents = new EventEmitter() + outgoingStubRejected = sinon.stub().rejects() + mock('./../lib/outgoing', outgoingStubRejected) + require('./../lib/xmpp')(logger, config) + simpleXmppEvents.emit('online', jid('bot@domain-xmpp.ltd/resource')) + xmppSendStub.resetHistory() + await simpleXmppEvents.emit('stanza', xml( + 'message', { + from: 'someone@domain-xmpp.ltd', + to: 'bot@domain-xmpp.ltd', + type: 'chat', + id: 'fcdd3d8c' + }, + xml( + 'body', { + }, + 'Is it working?') + )) + }) + it('Should send a XMPP reply if webhook failed', () => { + sinon.assert.calledOnce(outgoingStubRejected) + const args = outgoingStubRejected.args[0] + args.should.have.length(8) + args[3].should.equal('someone') + args[4].should.equal('someone@domain-xmpp.ltd') + args[5].should.equal('Is it working?') + args[6].should.equal('chat') + args[7].should.equal('w1') + sinon.assert.calledOnce(xmppSendStub) + const receiptStanza = xml( + 'message', { + to: 'someone@domain-xmpp.ltd', + type: 'chat' + }, + xml( + 'body', { + }, + 'Oops, something went wrong :(' + ) + ) + xmppSendStub.args[0][0].should.deep.equal(receiptStanza) + }) + }) + describe('Bot receive a message from himself in a room', () => { - it('Should not trigger outgoing webhook', (done) => { - simpleXmppEvents.emit('stanza', xml( + beforeEach(async () => { + await simpleXmppEvents.emit('stanza', xml( 'message', { from: 'roomname@conference.domain-xmpp.ltd/bot', to: 'roomname@conference.domain-xmpp.ltd', @@ -176,14 +236,16 @@ describe('XMPP component', () => { }, 'This is the message text') )) + }) + it('Should not trigger outgoing webhook', (done) => { sinon.assert.notCalled(outgoingStub) done() }) }) describe('Bot receive a message in an unknown room', () => { - it('Should not trigger outgoing webhook', (done) => { - simpleXmppEvents.emit('stanza', xml( + beforeEach(async () => { + await simpleXmppEvents.emit('stanza', xml( 'message', { from: 'unknownroomname@conference.domain-xmpp.ltd/someone', to: 'unknownroomname@conference.domain-xmpp.ltd', @@ -194,15 +256,16 @@ describe('XMPP component', () => { }, 'This is the message text') )) + }) + it('Should not trigger outgoing webhook', (done) => { sinon.assert.notCalled(outgoingStub) done() }) }) describe('Bot receive an old message in a room', () => { - it('Should not trigger outgoing webhook', (done) => { - simpleXmppEvents.emit('groupchat', 'roomname@conference.domain-xmpp.ltd', 'someone', 'This is the message text', 'stamp') - simpleXmppEvents.emit('stanza', xml( + beforeEach(async () => { + await simpleXmppEvents.emit('stanza', xml( 'message', { from: 'roomname@conference.domain-xmpp.ltd/someone', to: 'roomname@conference.domain-xmpp.ltd', @@ -219,14 +282,16 @@ describe('XMPP component', () => { }, 'This is the message text') )) + }) + it('Should not trigger outgoing webhook', (done) => { sinon.assert.notCalled(outgoingStub) done() }) }) describe('Bot receive a message in a room', () => { - it('Should trigger outgoing webhook with valid arguments', (done) => { - simpleXmppEvents.emit('stanza', xml( + beforeEach(async () => { + await simpleXmppEvents.emit('stanza', xml( 'message', { from: 'roomname@conference.domain-xmpp.ltd/someone', to: 'roomname@conference.domain-xmpp.ltd', @@ -237,6 +302,8 @@ describe('XMPP component', () => { }, 'This is the message text') )) + }) + it('Should trigger outgoing webhook with valid arguments', (done) => { sinon.assert.calledOnce(outgoingStub) const args = outgoingStub.args[0] args.should.have.length(8) @@ -250,9 +317,11 @@ describe('XMPP component', () => { }) describe('Send a message', () => { + beforeEach(async () => { + xmppSendStub.resetHistory() + await xmpp.send('someone@domain-xmpp.ltd', 'This is the message text sent by bot', 'chat') + }) it('Should call xmpp/client.send() with valid stanza', (done) => { - const xmpp = require('./../lib/xmpp')(logger, config) - xmpp.send('someone@domain-xmpp.ltd', 'This is the message text sent by bot', 'chat') sinon.assert.calledOnce(xmppSendStub) let stanza = xml( 'message', { @@ -272,29 +341,31 @@ describe('XMPP component', () => { }) describe('Close connection', () => { + beforeEach(async () => { + await xmpp.close() + }) it('Should call xmpp/client.stop()', (done) => { - const xmpp = require('./../lib/xmpp')(logger, config) - xmpp.close() sinon.assert.calledOnce(xmppCloseStub) done() }) }) describe('XMPP server send an error', () => { - before(() => { + let errorText + beforeEach(async () => { sinon.stub(process, 'exit') + errorText = 'This the error text' + await simpleXmppEvents.emit('error', new Error(errorText)) }) - after(() => { + afterEach(() => { process.exit.restore() }) it('Should log error and exit with 99 code', (done) => { - let error = 'This the error text' - simpleXmppEvents.emit('error', new Error(error)) require('fs').readFile(config.logger.file.path + config.logger.file.filename, 'utf8', (err, data) => { if (err) { throw err } - data.should.match(new RegExp('XMPP client encountered following error: ' + error + '\n$')) + data.should.match(new RegExp('XMPP client encountered following error: ' + errorText + '\n$')) sinon.assert.calledWith(process.exit, 99) done() })