diff --git a/src/commands/ban.ts b/src/commands/ban.ts index 30a17fe..fddac6c 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -4,6 +4,7 @@ import LogEmbed from "../helpers/embeds/LogEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Command } from "../type/command"; import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; export default class Ban extends Command { constructor() { @@ -15,13 +16,16 @@ export default class Ban extends Command { ]; } - public override async execute(context: ICommandContext) { + public override async execute(context: ICommandContext): Promise { const targetUser = context.message.mentions.users.first(); if (!targetUser) { const embed = new ErrorEmbed(context, "User does not exist"); embed.SendToCurrentChannel(); - return; + return { + commandContext: context, + embeds: [embed], + }; } const targetMember = context.message.guild?.member(targetUser); @@ -29,7 +33,10 @@ export default class Ban extends Command { if (!targetMember) { const embed = new ErrorEmbed(context, "User is not in this server"); embed.SendToCurrentChannel(); - return; + return { + commandContext: context, + embeds: [embed], + }; } const reasonArgs = context.args; @@ -38,13 +45,19 @@ export default class Ban extends Command { const reason = reasonArgs.join(" "); if (!context.message.guild?.available) { - return; + return { + commandContext: context, + embeds: [], + }; } if (!targetMember.bannable) { const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions); embed.SendToCurrentChannel(); - return; + return { + commandContext: context, + embeds: [embed], + }; } const logEmbed = new LogEmbed(context, "Member Banned"); @@ -58,5 +71,10 @@ export default class Ban extends Command { logEmbed.SendToModLogsChannel(); publicEmbed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [logEmbed, publicEmbed], + }; } } \ No newline at end of file diff --git a/src/contracts/ICommandReturnContext.ts b/src/contracts/ICommandReturnContext.ts new file mode 100644 index 0000000..144e981 --- /dev/null +++ b/src/contracts/ICommandReturnContext.ts @@ -0,0 +1,7 @@ +import { MessageEmbed } from "discord.js"; +import { ICommandContext } from "./ICommandContext"; + +export default interface ICommandReturnContext { + commandContext: ICommandContext, + embeds: MessageEmbed[] +} \ No newline at end of file diff --git a/tests/commands/ban.test.ts b/tests/commands/ban.test.ts new file mode 100644 index 0000000..eb621b0 --- /dev/null +++ b/tests/commands/ban.test.ts @@ -0,0 +1,567 @@ +import { Collection, Guild, GuildChannel, GuildChannelManager, GuildManager, GuildMember, Message, MessageEmbed, TextChannel, User } from "discord.js"; +import { mock } from "jest-mock-extended"; +import Ban from "../../src/commands/ban"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect values to be set', () => { + process.env.ROLES_MODERATOR = 'Moderator'; + + const ban = new Ban(); + + expect(ban._category).toBe('Moderation'); + expect(ban._roles.length).toBe(1); + expect(ban._roles[0]).toBe('Moderator'); + }); +}); + +describe('Execute', () => { + test('Given user has permission, expect user to be banned', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).toBeCalledTimes(1); + expect(mentionedMember.ban).toBeCalledWith({ reason: 'Test Reason' }); + }); + + test('Given user has permissions, expect embeds to be correct', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(result.embeds.length).toBe(2); + + const logEmbed = result.embeds[0]; + const publicEmbed = result.embeds[1]; + + expect(logEmbed.title).toBe('Member Banned'); + expect(publicEmbed.title).toBe(""); + expect(publicEmbed.description).toBe('[object Object] has been banned'); + expect(logEmbed.fields.length).toBe(3); + expect(publicEmbed.fields.length).toBe(0); + }); + + test('Given user has permission, expect logEmbed fields to be correct', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn().mockReturnValue('URL'), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + const logEmbed = result.embeds[0]; + + const fieldUser = logEmbed.fields[0]; + const fieldModerator = logEmbed.fields[1]; + const fieldReason = logEmbed.fields[2]; + + expect(fieldUser.name).toBe("User"); + expect(fieldUser.value).toBe("[object Object] `USERTAG`"); + expect(logEmbed.thumbnail?.url).toBe("URL"); + + expect(fieldModerator.name).toBe('Moderator'); + expect(fieldModerator.value).toBe('[object Object] `AUTHORTAG`'); + + expect(fieldReason.name).toBe('Reason'); + expect(fieldReason.value).toBe('Test Reason'); + }); + + test('Given user is not mentioned, expect error embed to be sent', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(null); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + expect(mentionedMember.ban).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + const embedError = result.embeds[0]; + + expect(embedError.description).toBe('User does not exist'); + }); + + test('Given member is not in server, expect error embed to be sent', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(null); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + expect(mentionedMember.ban).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + const embedError = result.embeds[0]; + + expect(embedError.description).toBe('User is not in this server'); + }); + + test('Given guild is unavailable, expect return and do nothing', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: true, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: false + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).not.toBeCalled(); + expect(logChannel.send).not.toBeCalled(); + expect(mentionedMember.ban).not.toBeCalled(); + expect(result.embeds.length).toBe(0); + }); + + test('Given bot cant ban user, expect error embed to be sent', async () => { + process.env = { + ROLES_MODERATOR: 'Moderator', + CHANNELS_LOGS_MOD: 'mod-logs' + }; + + const mentionedUser = { + displayAvatarURL: jest.fn(), + tag: 'USERTAG' + } as unknown as User; + const mentionedMember = { + bannable: false, + ban: jest.fn() + } as unknown as GuildMember; + const logChannel = { + name: 'mod-logs', + send: jest.fn() + } as unknown as TextChannel; + + const messageChannelSend = jest.fn(); + const messageMentionsUsersFirst = jest.fn() + .mockReturnValue(mentionedUser); + const messageGuildMember = jest.fn() + .mockReturnValue(mentionedMember); + const messageGuildChannelsCacheFind = jest.fn() + .mockImplementation((callback): TextChannel | undefined => { + const result = callback(logChannel); + + if (!result) { + return undefined; + } + + return logChannel; + }); + + const message = { + channel: { + send: messageChannelSend + }, + mentions: { + users: { + first: messageMentionsUsersFirst + } + }, + guild: { + member: messageGuildMember , + channels: { + cache: { + find: messageGuildChannelsCacheFind + } + }, + available: true + }, + author: { + tag: 'AUTHORTAG' + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'ban', + args: ['ban', 'Test', 'Reason'], + message: message + }; + + const ban = new Ban(); + + const result = await ban.execute(context); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(logChannel.send).not.toBeCalled(); + expect(mentionedMember.ban).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + const embedError = result.embeds[0]; + + expect(embedError.description).toBe('Unable to do this action, am I missing permissions?'); + }); +}); \ No newline at end of file