discord-irc/test/bot-events.test.js
Edward Jones f047ce0224
Upgrade discord.js to 12.2.0 (#555)
* Upgrade discord.js to 12.2.0

* Use cache accessors in Discord client

* Replace sendMessage with send

* Use avatarURL method

* Update test stubs for discord.js 12

* Disconnect clients at end of tests

* Fix disabling webhook mentions when bot lacks permissions
2020-08-09 02:07:09 -03:00

370 lines
15 KiB
JavaScript

/* eslint-disable no-unused-expressions, prefer-arrow-callback */
import chai from 'chai';
import sinonChai from 'sinon-chai';
import sinon from 'sinon';
import irc from 'irc-upd';
import discord from 'discord.js';
import Bot from '../lib/bot';
import logger from '../lib/logger';
import createDiscordStub from './stubs/discord-stub';
import createWebhookStub from './stubs/webhook-stub';
import ClientStub from './stubs/irc-client-stub';
import config from './fixtures/single-test-config.json';
chai.should();
chai.use(sinonChai);
describe('Bot Events', function () {
const sandbox = sinon.createSandbox({
useFakeTimers: false,
useFakeServer: false
});
const createBot = (optConfig = null) => {
const useConfig = optConfig || config;
const bot = new Bot(useConfig);
bot.sendToIRC = sandbox.stub();
bot.sendToDiscord = sandbox.stub();
bot.sendExactToDiscord = sandbox.stub();
return bot;
};
beforeEach(function () {
this.infoSpy = sandbox.stub(logger, 'info');
this.debugSpy = sandbox.stub(logger, 'debug');
this.warnSpy = sandbox.stub(logger, 'warn');
this.errorSpy = sandbox.stub(logger, 'error');
this.sendStub = sandbox.stub();
irc.Client = ClientStub;
discord.Client = createDiscordStub(this.sendStub);
discord.WebhookClient = createWebhookStub(this.sendStub);
ClientStub.prototype.send = sandbox.stub();
ClientStub.prototype.join = sandbox.stub();
this.bot = createBot();
this.bot.connect();
});
afterEach(function () {
this.bot.disconnect();
sandbox.restore();
});
it('should log on discord ready event', function () {
this.bot.discord.emit('ready');
this.infoSpy.should.have.been.calledWithExactly('Connected to Discord');
});
it('should log on irc registered event', function () {
const message = 'registered';
this.bot.ircClient.emit('registered', message);
this.infoSpy.should.have.been.calledWithExactly('Connected to IRC');
this.debugSpy.should.have.been.calledWithExactly('Registered event: ', message);
});
it('should try to send autoSendCommands on registered IRC event', function () {
this.bot.ircClient.emit('registered');
ClientStub.prototype.send.should.have.been.calledTwice;
ClientStub.prototype.send.getCall(0)
.args.should.deep.equal(config.autoSendCommands[0]);
ClientStub.prototype.send.getCall(1)
.args.should.deep.equal(config.autoSendCommands[1]);
});
it('should error log on error events', function () {
const discordError = new Error('discord');
const ircError = new Error('irc');
this.bot.discord.emit('error', discordError);
this.bot.ircClient.emit('error', ircError);
this.errorSpy.getCall(0).args[0].should.equal('Received error event from Discord');
this.errorSpy.getCall(0).args[1].should.equal(discordError);
this.errorSpy.getCall(1).args[0].should.equal('Received error event from IRC');
this.errorSpy.getCall(1).args[1].should.equal(ircError);
});
it('should warn log on warn events from discord', function () {
const discordError = new Error('discord');
this.bot.discord.emit('warn', discordError);
const [message, error] = this.warnSpy.firstCall.args;
message.should.equal('Received warn event from Discord');
error.should.equal(discordError);
});
it('should send messages to irc if correct', function () {
const message = {
type: 'message'
};
this.bot.discord.emit('message', message);
this.bot.sendToIRC.should.have.been.calledWithExactly(message);
});
it('should send messages to discord', function () {
const channel = '#channel';
const author = 'user';
const text = 'hi';
this.bot.ircClient.emit('message', author, channel, text);
this.bot.sendToDiscord.should.have.been.calledWithExactly(author, channel, text);
});
it('should send notices to discord', function () {
const channel = '#channel';
const author = 'user';
const text = 'hi';
const formattedText = `*${text}*`;
this.bot.ircClient.emit('notice', author, channel, text);
this.bot.sendToDiscord.should.have.been.calledWithExactly(author, channel, formattedText);
});
it('should not send name change event to discord', function () {
const channel = '#channel';
const oldnick = 'user1';
const newnick = 'user2';
this.bot.ircClient.emit('nick', oldnick, newnick, [channel]);
this.bot.sendExactToDiscord.should.not.have.been.called;
});
it('should send name change event to discord', function () {
const channel1 = '#channel1';
const channel2 = '#channel2';
const channel3 = '#channel3';
const oldNick = 'user1';
const newNick = 'user2';
const user3 = 'user3';
const bot = createBot({ ...config, ircStatusNotices: true });
const staticChannel = new Set([bot.nickname, user3]);
bot.connect();
bot.ircClient.emit('names', channel1, { [bot.nickname]: '', [oldNick]: '' });
bot.ircClient.emit('names', channel2, { [bot.nickname]: '', [user3]: '' });
const channelNicksPre = new Set([bot.nickname, oldNick]);
bot.channelUsers.should.deep.equal({ '#channel1': channelNicksPre, '#channel2': staticChannel });
const formattedText = `*${oldNick}* is now known as ${newNick}`;
const channelNicksAfter = new Set([bot.nickname, newNick]);
bot.ircClient.emit('nick', oldNick, newNick, [channel1, channel2, channel3]);
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel1, formattedText);
bot.channelUsers.should.deep.equal({ '#channel1': channelNicksAfter, '#channel2': staticChannel });
});
it('should send actions to discord', function () {
const channel = '#channel';
const author = 'user';
const text = 'hi';
const formattedText = '_hi_';
const message = {};
this.bot.ircClient.emit('action', author, channel, text, message);
this.bot.sendToDiscord.should.have.been.calledWithExactly(author, channel, formattedText);
});
it('should keep track of users through names event when irc status notices enabled', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
bot.channelUsers.should.be.an('object');
const channel = '#channel';
// nick => '' means the user is not a special user
const nicks = {
[bot.nickname]: '', user: '', user2: '@', user3: '+'
};
bot.ircClient.emit('names', channel, nicks);
const channelNicks = new Set([bot.nickname, 'user', 'user2', 'user3']);
bot.channelUsers.should.deep.equal({ '#channel': channelNicks });
});
it('should lowercase the channelUsers mapping', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel = '#channelName';
const nicks = { [bot.nickname]: '' };
bot.ircClient.emit('names', channel, nicks);
const channelNicks = new Set([bot.nickname]);
bot.channelUsers.should.deep.equal({ '#channelname': channelNicks });
});
it('should send join messages to discord when config enabled', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel = '#channel';
bot.ircClient.emit('names', channel, { [bot.nickname]: '' });
const nick = 'user';
const text = `*${nick}* has joined the channel`;
bot.ircClient.emit('join', channel, nick);
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel, text);
const channelNicks = new Set([bot.nickname, nick]);
bot.channelUsers.should.deep.equal({ '#channel': channelNicks });
});
it('should not announce itself joining by default', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel = '#channel';
bot.ircClient.emit('names', channel, { [bot.nickname]: '' });
const nick = bot.nickname;
bot.ircClient.emit('join', channel, nick);
bot.sendExactToDiscord.should.not.have.been.called;
const channelNicks = new Set([bot.nickname]);
bot.channelUsers.should.deep.equal({ '#channel': channelNicks });
});
it('should announce the bot itself when config enabled', function () {
// self-join is announced before names (which includes own nick)
// hence don't trigger a names and don't expect anything of bot.channelUsers
const bot = createBot({ ...config, ircStatusNotices: true, announceSelfJoin: true });
bot.connect();
const channel = '#channel';
const nick = this.bot.nickname;
const text = `*${nick}* has joined the channel`;
bot.ircClient.emit('join', channel, nick);
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel, text);
});
it('should send part messages to discord when config enabled', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel = '#channel';
const nick = 'user';
bot.ircClient.emit('names', channel, { [bot.nickname]: '', [nick]: '' });
const originalNicks = new Set([bot.nickname, nick]);
bot.channelUsers.should.deep.equal({ '#channel': originalNicks });
const reason = 'Leaving';
const text = `*${nick}* has left the channel (${reason})`;
bot.ircClient.emit('part', channel, nick, reason);
bot.sendExactToDiscord.should.have.been.calledWithExactly(channel, text);
// it should remove the nickname from the channelUsers list
const channelNicks = new Set([bot.nickname]);
bot.channelUsers.should.deep.equal({ '#channel': channelNicks });
});
it('should not announce itself leaving a channel', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel = '#channel';
bot.ircClient.emit('names', channel, { [bot.nickname]: '', user: '' });
const originalNicks = new Set([bot.nickname, 'user']);
bot.channelUsers.should.deep.equal({ '#channel': originalNicks });
const reason = 'Leaving';
bot.ircClient.emit('part', channel, bot.nickname, reason);
bot.sendExactToDiscord.should.not.have.been.called;
// it should remove the nickname from the channelUsers list
bot.channelUsers.should.deep.equal({});
});
it('should only send quit messages to discord for channels the user is tracked in', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel1 = '#channel1';
const channel2 = '#channel2';
const channel3 = '#channel3';
const nick = 'user';
bot.ircClient.emit('names', channel1, { [bot.nickname]: '', [nick]: '' });
bot.ircClient.emit('names', channel2, { [bot.nickname]: '' });
bot.ircClient.emit('names', channel3, { [bot.nickname]: '', [nick]: '' });
const reason = 'Quit: Leaving';
const text = `*${nick}* has quit (${reason})`;
// send quit message for all channels on server, as the node-irc library does
bot.ircClient.emit('quit', nick, reason, [channel1, channel2, channel3]);
bot.sendExactToDiscord.should.have.been.calledTwice;
bot.sendExactToDiscord.getCall(0).args.should.deep.equal([channel1, text]);
bot.sendExactToDiscord.getCall(1).args.should.deep.equal([channel3, text]);
});
it('should not crash with join/part/quit messages and weird channel casing', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
function wrap() {
const nick = 'user';
const reason = 'Leaving';
bot.ircClient.emit('names', '#Channel', { [bot.nickname]: '' });
bot.ircClient.emit('join', '#cHannel', nick);
bot.ircClient.emit('part', '#chAnnel', nick, reason);
bot.ircClient.emit('join', '#chaNnel', nick);
bot.ircClient.emit('quit', nick, reason, ['#chanNel']);
}
(wrap).should.not.throw();
});
it('should be possible to disable join/part/quit messages', function () {
const bot = createBot({ ...config, ircStatusNotices: false });
bot.connect();
const channel = '#channel';
const nick = 'user';
const reason = 'Leaving';
bot.ircClient.emit('names', channel, { [bot.nickname]: '' });
bot.ircClient.emit('join', channel, nick);
bot.ircClient.emit('part', channel, nick, reason);
bot.ircClient.emit('join', channel, nick);
bot.ircClient.emit('quit', nick, reason, [channel]);
bot.sendExactToDiscord.should.not.have.been.called;
});
it('should warn if it receives a part/quit before a names event', function () {
const bot = createBot({ ...config, ircStatusNotices: true });
bot.connect();
const channel = '#channel';
const reason = 'Leaving';
bot.ircClient.emit('part', channel, 'user1', reason);
bot.ircClient.emit('quit', 'user2', reason, [channel]);
this.warnSpy.should.have.been.calledTwice;
this.warnSpy.getCall(0).args.should.deep.equal([`No channelUsers found for ${channel} when user1 parted.`]);
this.warnSpy.getCall(1).args.should.deep.equal([`No channelUsers found for ${channel} when user2 quit, ignoring.`]);
});
it('should not crash if it uses a different name from config', function () {
// this can happen when a user with the same name is already connected
const bot = createBot({ ...config, nickname: 'testbot' });
bot.connect();
const newName = 'testbot1';
bot.ircClient.nick = newName;
function wrap() {
bot.ircClient.emit('join', '#channel', newName);
}
(wrap).should.not.throw;
});
it('should not listen to discord debug messages in production', function () {
logger.level = 'info';
const bot = createBot();
bot.connect();
const listeners = bot.discord.listeners('debug');
listeners.length.should.equal(0);
});
it('should listen to discord debug messages in development', function () {
logger.level = 'debug';
const bot = createBot();
bot.connect();
const listeners = bot.discord.listeners('debug');
listeners.length.should.equal(1);
});
it('should join channels when invited', function () {
const channel = '#irc';
const author = 'user';
this.bot.ircClient.emit('invite', channel, author);
const firstCall = this.debugSpy.getCall(1);
firstCall.args[0].should.equal('Received invite:');
firstCall.args[1].should.equal(channel);
firstCall.args[2].should.equal(author);
ClientStub.prototype.join.should.have.been.calledWith(channel);
const secondCall = this.debugSpy.getCall(2);
secondCall.args[0].should.equal('Joining channel:');
secondCall.args[1].should.equal(channel);
});
it('should not join channels that aren\'t in the channel mapping', function () {
const channel = '#wrong';
const author = 'user';
this.bot.ircClient.emit('invite', channel, author);
const firstCall = this.debugSpy.getCall(1);
firstCall.args[0].should.equal('Received invite:');
firstCall.args[1].should.equal(channel);
firstCall.args[2].should.equal(author);
ClientStub.prototype.join.should.not.have.been.called;
const secondCall = this.debugSpy.getCall(2);
secondCall.args[0].should.equal('Channel not found in config, not joining:');
secondCall.args[1].should.equal(channel);
});
});