diff --git a/src/commands/role.ts b/src/commands/role.ts index a5992f7..e9e7475 100644 --- a/src/commands/role.ts +++ b/src/commands/role.ts @@ -3,6 +3,7 @@ import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Role as DiscordRole } from "discord.js"; import { Command } from "../type/command"; import { ICommandContext } from "../contracts/ICommandContext"; +import ICommandReturnContext from "../contracts/ICommandReturnContext"; export default class Role extends Command { constructor() { @@ -11,30 +12,39 @@ export default class Role extends Command { super._category = "General"; } - public override execute(context: ICommandContext) { + public override async execute(context: ICommandContext) { const roles = process.env.COMMANDS_ROLE_ROLES!.split(','); if (context.args.length == 0) { this.SendRolesList(context, roles); } else { - this.ToggleRole(context, roles); + await this.ToggleRole(context, roles); } } - private SendRolesList(context: ICommandContext, roles: String[]) { + public SendRolesList(context: ICommandContext, roles: String[]): ICommandReturnContext { const description = `Do ${process.env.BOT_PREFIX}role to get the role!\n${roles.join('\n')}`; const embed = new PublicEmbed(context, "Roles", description); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [embed] + }; } - private ToggleRole(context: ICommandContext, roles: String[]) { + public async ToggleRole(context: ICommandContext, roles: String[]): Promise { const requestedRole = context.args[0]; if (!roles.includes(requestedRole)) { const errorEmbed = new ErrorEmbed(context, "This role isn't marked as assignable, to see a list of assignable roles, run this command without any parameters"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [errorEmbed] + }; } const assignRole = context.message.guild?.roles.cache.find(x => x.name == requestedRole); @@ -42,29 +52,48 @@ export default class Role extends Command { if (!assignRole) { const errorEmbed = new ErrorEmbed(context, "The current server doesn't have this role. Please contact the server's moderators"); errorEmbed.SendToCurrentChannel(); - return; + + return { + commandContext: context, + embeds: [errorEmbed] + }; } const role = context.message.member?.roles.cache.find(x => x.name == requestedRole) if (!role) { - this.AddRole(context, assignRole); + await this.AddRole(context, assignRole); } else { - this.RemoveRole(context, assignRole); + await this.RemoveRole(context, assignRole); } + + return { + commandContext: context, + embeds: [] + }; } - private async AddRole(context: ICommandContext, role: DiscordRole) { + public async AddRole(context: ICommandContext, role: DiscordRole): Promise { await context.message.member?.roles.add(role); const embed = new PublicEmbed(context, "", `Gave role: ${role.name}`); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [embed] + }; } - private async RemoveRole(context: ICommandContext, role: DiscordRole) { + public async RemoveRole(context: ICommandContext, role: DiscordRole): Promise { await context.message.member?.roles.remove(role); const embed = new PublicEmbed(context, "", `Removed role: ${role.name}`); embed.SendToCurrentChannel(); + + return { + commandContext: context, + embeds: [embed] + }; } } \ No newline at end of file diff --git a/tests/commands/role.test.ts b/tests/commands/role.test.ts new file mode 100644 index 0000000..f72d5da --- /dev/null +++ b/tests/commands/role.test.ts @@ -0,0 +1,411 @@ +import { GuildMemberRoleManager, Message, Role as DiscordRole } from "discord.js"; +import { mock } from "jest-mock-extended"; +import Role from "../../src/commands/role"; +import { ICommandContext } from "../../src/contracts/ICommandContext"; + +beforeEach(() => { + process.env = {}; +}); + +describe('Constructor', () => { + test('Expect properties are set', () => { + const role = new Role(); + + expect(role._category).toBe("General"); + }); +}); + +describe('Execute', () => { + test('Given no arguments were given, expect SendRolesList to be executed', async () => { + process.env = { + COMMANDS_ROLE_ROLES: 'One,Two' + }; + + const message = {} as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: [], + message: message + }; + + const role = new Role(); + + role.SendRolesList = jest.fn(); + role.ToggleRole = jest.fn(); + + await role.execute(context); + + expect(role.SendRolesList).toBeCalledWith(context, ['One', 'Two']); + expect(role.ToggleRole).not.toBeCalled(); + }); + + test('Given an argument was given, expect ToggleRole to be executed', async () => { + process.env = { + COMMANDS_ROLE_ROLES: 'One,Two' + }; + + const message = {} as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const role = new Role(); + + role.SendRolesList = jest.fn(); + role.ToggleRole = jest.fn(); + + await role.execute(context); + + expect(role.SendRolesList).not.toBeCalled(); + expect(role.ToggleRole).toBeCalledWith(context, ['One', 'Two']); + }); +}); + +describe('SendRolesList', () => { + test('Expect embed with roles to be sent to the current channel', () => { + process.env = { + BOT_PREFIX: '!' + }; + + const messageChannelSend = jest.fn(); + + const message = { + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: [], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + const result = role.SendRolesList(context, roles); + + expect(messageChannelSend).toBeCalledTimes(1); + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe('Roles'); + expect(embed.description).toBe('Do !role to get the role!\nOne\nTwo'); + }); +}); + +describe('ToggleRole', () => { + test('Given role name is a valid role AND user does not have the role, expect role to be added', async () => { + const discordRole = {} as unknown as DiscordRole; + + const messageMemberRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(discordRole); + const messageChannelSend = jest.fn(); + + const message = { + member: { + roles: { + cache: { + find: messageMemberRolesCacheFind + } + } + }, + guild: { + roles: { + cache: { + find: messageGuildRolesCacheFind + } + } + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + role.AddRole = jest.fn(); + role.RemoveRole = jest.fn(); + + const result = await role.ToggleRole(context, roles); + + expect(messageMemberRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).not.toBeCalled(); + expect(role.AddRole).toBeCalledWith(context, discordRole); + expect(role.RemoveRole).not.toBeCalled(); + + expect(result.embeds.length).toBe(0); + }); + + test('Given role name is a valid role AND user has the role, expect role to be removed', async () => { + const discordRole = {} as unknown as DiscordRole; + + const messageMemberRolesCacheFind = jest.fn() + .mockReturnValue(discordRole); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(discordRole); + const messageChannelSend = jest.fn(); + + const message = { + member: { + roles: { + cache: { + find: messageMemberRolesCacheFind + } + } + }, + guild: { + roles: { + cache: { + find: messageGuildRolesCacheFind + } + } + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + role.AddRole = jest.fn(); + role.RemoveRole = jest.fn(); + + const result = await role.ToggleRole(context, roles); + + expect(messageMemberRolesCacheFind).toBeCalledTimes(1); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).not.toBeCalled(); + expect(role.AddRole).not.toBeCalled(); + expect(role.RemoveRole).toBeCalledWith(context, discordRole); + + expect(result.embeds.length).toBe(0); + }); + + test('Given role requested is not in the roles array, expect role not assignable error', async () => { + const discordRole = {} as unknown as DiscordRole; + + const messageMemberRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(discordRole); + const messageChannelSend = jest.fn(); + + const message = { + member: { + roles: { + cache: { + find: messageMemberRolesCacheFind + } + } + }, + guild: { + roles: { + cache: { + find: messageGuildRolesCacheFind + } + } + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['Three'], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + role.AddRole = jest.fn(); + role.RemoveRole = jest.fn(); + + const result = await role.ToggleRole(context, roles); + + expect(messageMemberRolesCacheFind).not.toBeCalled(); + expect(messageGuildRolesCacheFind).not.toBeCalled(); + expect(messageChannelSend).toBeCalledTimes(1); + expect(role.AddRole).not.toBeCalled(); + expect(role.RemoveRole).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe("This role isn't marked as assignable, to see a list of assignable roles, run this command without any parameters"); + }); + + test('Given the role is not in the guild, expect error', async () => { + const discordRole = {} as unknown as DiscordRole; + + const messageMemberRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageGuildRolesCacheFind = jest.fn() + .mockReturnValue(undefined); + const messageChannelSend = jest.fn(); + + const message = { + member: { + roles: { + cache: { + find: messageMemberRolesCacheFind + } + } + }, + guild: { + roles: { + cache: { + find: messageGuildRolesCacheFind + } + } + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const roles = ['One', 'Two']; + + const role = new Role(); + + role.AddRole = jest.fn(); + role.RemoveRole = jest.fn(); + + const result = await role.ToggleRole(context, roles); + + expect(messageMemberRolesCacheFind).not.toBeCalled(); + expect(messageGuildRolesCacheFind).toBeCalledTimes(1); + expect(messageChannelSend).toBeCalledTimes(1); + expect(role.AddRole).not.toBeCalled(); + expect(role.RemoveRole).not.toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Error Embed + const errorEmbed = result.embeds[0]; + + expect(errorEmbed.description).toBe("The current server doesn't have this role. Please contact the server's moderators"); + }); +}); + +describe('AddRole', () => { + test('Expect role to be added to user', async () => { + const messageChannelSend = jest.fn(); + + const guildMemberRoleManager = mock(); + + const message = { + member: { + roles: guildMemberRoleManager + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const discordRole = { + name: 'One' + } as unknown as DiscordRole; + + const role = new Role(); + + const result = await role.AddRole(context, discordRole); + + expect(guildMemberRoleManager.add).toBeCalledWith(discordRole); + expect(messageChannelSend).toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe(''); + expect(embed.description).toBe('Gave role: One'); + }); +}); + +describe('RemoveRole', () => { + test('Expect role to be removed from user', async () => { + const messageChannelSend = jest.fn(); + + const guildMemberRoleManager = mock(); + + const message = { + member: { + roles: guildMemberRoleManager + }, + channel: { + send: messageChannelSend + } + } as unknown as Message; + + const context: ICommandContext = { + name: 'role', + args: ['One'], + message: message + }; + + const discordRole = { + name: 'One' + } as unknown as DiscordRole; + + const role = new Role(); + + const result = await role.RemoveRole(context, discordRole); + + expect(guildMemberRoleManager.remove).toBeCalledWith(discordRole); + expect(messageChannelSend).toBeCalled(); + + expect(result.embeds.length).toBe(1); + + // Embed + const embed = result.embeds[0]; + + expect(embed.title).toBe(''); + expect(embed.description).toBe('Removed role: One'); + }); +}); \ No newline at end of file