mirror of
https://github.com/nioc/xmpp-bot.git
synced 2024-11-10 03:56:48 +01:00
404 lines
12 KiB
JavaScript
404 lines
12 KiB
JavaScript
'use strict'
|
|
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', () => {
|
|
let logger, config, outgoingStub, outgoingStubRejected, xmppSendStub, xmppCloseStub, simpleXmppEvents, xmpp
|
|
|
|
before('Setup logger and config', (done) => {
|
|
// create default logger
|
|
logger = require('./../lib/logger')()
|
|
|
|
// get configuration
|
|
config = require('./../lib/config')(logger, './test/config.json')
|
|
|
|
// 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()
|
|
mock('@xmpp/client', {
|
|
client: () => {
|
|
this.start = async () => {}
|
|
this.stop = xmppCloseStub
|
|
this.send = xmppSendStub
|
|
this.on = (eventName, callback) => {
|
|
simpleXmppEvents.on(eventName, callback)
|
|
}
|
|
return this
|
|
},
|
|
xml: require('@xmpp/xml'),
|
|
jid: require('@xmpp/jid')
|
|
})
|
|
|
|
// 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) => {
|
|
sinon.assert.called(xmppSendStub)
|
|
// 1 "send" call for presence and n "send" calls for joining rooms
|
|
let roomsLength = config.xmpp.rooms.length
|
|
sinon.assert.callCount(xmppSendStub, roomsLength + 1)
|
|
for (let index = 1; index < roomsLength + 1; index++) {
|
|
const args = xmppSendStub.args[index]
|
|
args.should.have.length(1)
|
|
let occupantJid = config.xmpp.rooms[index - 1].id + '/' + 'bot'
|
|
const stanza = xml(
|
|
'presence', {
|
|
to: occupantJid
|
|
},
|
|
xml(
|
|
'x', {
|
|
xmlns: 'http://jabber.org/protocol/muc'
|
|
}
|
|
)
|
|
)
|
|
args[0].should.deep.equal(stanza)
|
|
}
|
|
done()
|
|
})
|
|
it('Should trace connection status', (done) => {
|
|
sinon.assert.calledWith(logger.trace, 'Status changed to connecting')
|
|
done()
|
|
})
|
|
})
|
|
|
|
describe('Connect to XMPP server but fail to start', () => {
|
|
let xmppStartStub
|
|
beforeEach(async () => {
|
|
xmppStartStub = sinon.stub().rejects(new Error('Stubed error message'))
|
|
mock('@xmpp/client', {
|
|
client: () => {
|
|
this.start = xmppStartStub
|
|
this.stop = xmppCloseStub
|
|
this.send = xmppSendStub
|
|
this.on = (eventName, callback) => {
|
|
simpleXmppEvents.on(eventName, callback)
|
|
}
|
|
return this
|
|
},
|
|
xml: require('@xmpp/xml'),
|
|
jid: require('@xmpp/jid')
|
|
})
|
|
xmpp = require('./../lib/xmpp')(logger, config)
|
|
})
|
|
it('Should log error', (done) => {
|
|
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 at connection: Stubed error message' + '\n$'))
|
|
done()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Bot receive a presence stanza from someone', () => {
|
|
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', () => {
|
|
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', () => {
|
|
beforeEach(async () => {
|
|
xmppSendStub.resetHistory()
|
|
await simpleXmppEvents.emit('stanza', xml(
|
|
'message', {
|
|
from: 'someone@domain-xmpp.ltd',
|
|
to: 'bot@domain-xmpp.ltd',
|
|
type: 'chat',
|
|
id: 'fcdd3d8c'
|
|
},
|
|
xml(
|
|
'body', {
|
|
},
|
|
'This is the message text'),
|
|
xml(
|
|
'request', {
|
|
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)
|
|
args[3].should.equal('someone')
|
|
args[4].should.equal('someone@domain-xmpp.ltd')
|
|
args[5].should.equal('This is the message text')
|
|
args[6].should.equal('chat')
|
|
args[7].should.equal('w1')
|
|
sinon.assert.calledOnce(xmppSendStub)
|
|
const receiptStanza = xml(
|
|
'message', {
|
|
to: 'someone@domain-xmpp.ltd'
|
|
},
|
|
xml(
|
|
'received', {
|
|
xmlns: 'urn:xmpp:receipts',
|
|
id: 'fcdd3d8c'
|
|
}
|
|
)
|
|
)
|
|
xmppSendStub.args[0][0].should.deep.equal(receiptStanza)
|
|
done()
|
|
})
|
|
})
|
|
|
|
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', () => {
|
|
beforeEach(async () => {
|
|
await simpleXmppEvents.emit('stanza', xml(
|
|
'message', {
|
|
from: 'roomname@conference.domain-xmpp.ltd/bot',
|
|
to: 'roomname@conference.domain-xmpp.ltd',
|
|
type: 'groupchat'
|
|
},
|
|
xml(
|
|
'body', {
|
|
},
|
|
'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', () => {
|
|
beforeEach(async () => {
|
|
await simpleXmppEvents.emit('stanza', xml(
|
|
'message', {
|
|
from: 'unknownroomname@conference.domain-xmpp.ltd/someone',
|
|
to: 'unknownroomname@conference.domain-xmpp.ltd',
|
|
type: 'groupchat'
|
|
},
|
|
xml(
|
|
'body', {
|
|
},
|
|
'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', () => {
|
|
beforeEach(async () => {
|
|
await simpleXmppEvents.emit('stanza', xml(
|
|
'message', {
|
|
from: 'roomname@conference.domain-xmpp.ltd/someone',
|
|
to: 'roomname@conference.domain-xmpp.ltd',
|
|
type: 'groupchat'
|
|
},
|
|
xml(
|
|
'body', {
|
|
},
|
|
'This is the message text'),
|
|
xml(
|
|
'delay', {
|
|
xmlns: 'urn:xmpp:delay',
|
|
from: 'roomname@conference.domain-xmpp.ltd'
|
|
},
|
|
'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', () => {
|
|
beforeEach(async () => {
|
|
await simpleXmppEvents.emit('stanza', xml(
|
|
'message', {
|
|
from: 'roomname@conference.domain-xmpp.ltd/someone',
|
|
to: 'roomname@conference.domain-xmpp.ltd',
|
|
type: 'groupchat'
|
|
},
|
|
xml(
|
|
'body', {
|
|
},
|
|
'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)
|
|
args[3].should.equal('someone')
|
|
args[4].should.equal('roomname@conference.domain-xmpp.ltd')
|
|
args[5].should.equal('This is the message text')
|
|
args[6].should.equal('groupchat')
|
|
args[7].should.equal('w1')
|
|
done()
|
|
})
|
|
})
|
|
|
|
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) => {
|
|
sinon.assert.calledOnce(xmppSendStub)
|
|
let stanza = xml(
|
|
'message', {
|
|
to: 'someone@domain-xmpp.ltd',
|
|
type: 'chat'
|
|
},
|
|
xml(
|
|
'body', {
|
|
},
|
|
'This is the message text sent by bot')
|
|
)
|
|
const args = xmppSendStub.args[0]
|
|
args.should.have.length(1)
|
|
args[0].should.deep.equal(stanza)
|
|
done()
|
|
})
|
|
})
|
|
|
|
describe('Close connection', () => {
|
|
beforeEach(async () => {
|
|
await xmpp.close()
|
|
})
|
|
it('Should call xmpp/client.stop()', (done) => {
|
|
sinon.assert.calledOnce(xmppCloseStub)
|
|
done()
|
|
})
|
|
})
|
|
|
|
describe('XMPP server send an error', () => {
|
|
let errorText
|
|
beforeEach(async () => {
|
|
sinon.stub(process, 'exit')
|
|
errorText = 'This the error text'
|
|
await simpleXmppEvents.emit('error', new Error(errorText))
|
|
})
|
|
afterEach(() => {
|
|
process.exit.restore()
|
|
})
|
|
it('Should log error and exit with 99 code', (done) => {
|
|
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: ' + errorText + '\n$'))
|
|
sinon.assert.calledWith(process.exit, 99)
|
|
done()
|
|
})
|
|
})
|
|
})
|
|
})
|