diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index c95e94b..0968bb9 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -19,18 +19,23 @@ import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.Interaction; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.InteractionType; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.components.ItemComponent; import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.modals.Modal; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; import net.dv8tion.jda.api.requests.restaction.MessageEditAction; +import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; import net.dv8tion.jda.api.requests.restaction.interactions.ModalCallbackAction; @@ -46,6 +51,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.Consumer; import org.junit.jupiter.api.Assertions; @@ -79,10 +85,11 @@ public class MockJDA { public static final Guild GUILD = mockGuild(); public static Modal CURRENT_MODAL = null; + public static final TextChannel GENERAL = mockChannel("general"); // Not found, let it be -1 public static final TextChannel REQUEST_CHANNEL = mockChannel("request_access"); public static final TextChannel ACCEPTED_CHANNEL = mockChannel("accepted_requests"); public static final TextChannel REJECTED_CHANNEL = mockChannel("rejected_requests"); - public static final List CHANNELS = List.of(REQUEST_CHANNEL, ACCEPTED_CHANNEL, REJECTED_CHANNEL); + public static final List CHANNELS = List.of(GENERAL, REQUEST_CHANNEL, ACCEPTED_CHANNEL, REJECTED_CHANNEL); public static final Role ADMINISTRATOR = mockRole("Administrator", 405917902865170453L, 4); public static final Role MAINTAINER = mockRole("Maintainer", 659568973079379971L, 3); @@ -251,6 +258,7 @@ public static Message mockMessage(String content, MessageChannel channel, long i latestEmbeds.remove(channel.getIdLong()); when(message.getContentRaw()).thenAnswer(inv -> messages.get(message.getIdLong())); + when(message.getJumpUrl()).thenReturn(""); when(message.getIdLong()).thenReturn(id); when(message.getId()).thenReturn(Long.toString(id)); when(message.getGuild()).thenReturn(GUILD); @@ -268,6 +276,9 @@ public static Message mockMessage(String content, MessageChannel channel, long i messages.put(message.getIdLong(), inv.getArgument(0)); return mockWebhookReply(MessageEditAction.class, mockInteractionHook(message.getMember(), channel, InteractionType.COMMAND), message); }); + + when(message.createThreadChannel(anyString())).thenAnswer(inv -> mock(ThreadChannelAction.class)); + when(message.addReaction(any())).thenAnswer(inv -> mockAction(null)); return message; } @@ -422,7 +433,7 @@ public static void assertEmbed(MessageEmbed embed, MessageEmbed expectedOutput, public static long assertSlashCommandEvent(TestCommandListener listener, Map options, MessageEmbed... outputs) { BotCommand command = listener.getCommand(); - SlashCommandEvent event = mockSlashCommandEvent(REQUEST_CHANNEL, command, options); + SlashCommandEvent event = mockSlashCommandEvent(GENERAL, command, options); return assertSlashCommandEvent(event, listener, outputs); } @@ -485,27 +496,8 @@ public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, Bo return options.containsKey(key) ? options.get(key) : def; }); - when(event.reply(anyString())).thenAnswer(invocation -> - mockReply(mockMessage(invocation.getArgument(0), channel, id))); - when(event.reply(any(MessageCreateData.class))).thenAnswer(invocation -> - mockReply(mockMessage(invocation.getArgument(0, MessageCreateData.class).getContent(), channel, id))); - when(event.replyEmbeds(anyList())).thenAnswer(invocation -> - mockReply(mockMessage(null, invocation.getArgument(0), channel, id))); - when(event.replyEmbeds(any(MessageEmbed.class), any(MessageEmbed[].class))).thenAnswer(invocation -> { - List embeds = invocation.getArguments().length == 1 ? new ArrayList<>() : Arrays.asList(invocation.getArgument(1)); - embeds.add(invocation.getArgument(0)); - return mockReply(mockMessage(null, embeds, channel, id)); - }); - - when(event.deferReply()).thenAnswer(invocation -> - mockReply(mockMessage(null, channel, id)) - ); - - when(event.deferReply(anyBoolean())).thenAnswer(invocation -> - mockReply(mockMessage(null, channel, id)) - ); - - when(event.replyModal(any())).thenAnswer(inv -> { + mockReplyCallbacks(event, channel, id); + when(event.replyModal(any(Modal.class))).thenAnswer(inv -> { CURRENT_MODAL = inv.getArgument(0); return mockModalReply(user, channel); }); @@ -532,6 +524,7 @@ public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, TextChannel channel = message.getChannel().asTextChannel(); long id = RANDOM.nextLong(0, Long.MAX_VALUE); + when(event.isFromGuild()).thenReturn(true); when(event.getGuild()).thenReturn(GUILD); when(event.getMessageChannel()).thenReturn(channel); when(event.getButton()).thenReturn(button); @@ -551,6 +544,74 @@ public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, when(event.getMessageId()).thenAnswer(inv -> message.getId()); when(event.getMessageIdLong()).thenAnswer(inv -> message.getIdLong()); + mockReplyCallbacks(event, channel, id); + when(event.replyModal(any(Modal.class))).thenAnswer(inv -> { + CURRENT_MODAL = inv.getArgument(0); + return mockModalReply(user, channel); + }); + + return event; + } + + public static long assertModalInteractionEvent(ListenerAdapter listener, Modal modal, MessageChannel channel, Map options, MessageEmbed... outputs) { + ModalInteractionEvent event = mockModalInteractionEvent(modal, channel, options); + return assertModalInteractionEvent(listener, event, outputs); + } + + public static long assertModalInteractionEvent(ListenerAdapter listener, ModalInteractionEvent event, MessageEmbed... outputs) { + listener.onModalInteraction(event); + + if (outputs != null) + assertEmbeds(event.getIdLong(), Arrays.asList(outputs), true); + + return event.getIdLong(); + } + + public static ModalInteractionEvent mockModalInteractionEvent(Modal modal, MessageChannel channel, Map options) { + ModalInteractionEvent event = mock(ModalInteractionEvent.class); + long id = RANDOM.nextLong(0, Long.MAX_VALUE); + + when(event.getJDA()).thenReturn(JDA); + when(event.isFromGuild()).thenReturn(true); + when(event.getGuild()).thenReturn(GUILD); + when(event.getModalId()).thenAnswer(inv -> modal.getId()); + when(event.getChannel()).thenAnswer(inv -> mockUnion(channel)); + when(event.getMessageChannel()).thenReturn(channel); + + when(event.getValue(anyString())).thenAnswer(inv -> { + String key = inv.getArgument(0); + if (!options.containsKey(key)) return null; + + ModalMapping mapping = mock(ModalMapping.class); + when(mapping.getId()).thenReturn(key); + when(mapping.getAsString()).thenReturn(options.get(key)); + + return mapping; + }); + + Member user = mockMember("User"); + GUILD.addRoleToMember(user, AUTHOR); + GUILD.addRoleToMember(user, REVIEWER); + GUILD.addRoleToMember(user, MAINTAINER); + GUILD.addRoleToMember(user, ADMINISTRATOR); + + when(event.getMember()).thenReturn(user); + when(event.getUser()).thenAnswer(inv -> user.getUser()); + when(event.getIdLong()).thenReturn(id); + + mockReplyCallbacks(event, channel, id); + return event; + } + + public static Modal mockModal(String id, String title) { + Modal modal = mock(Modal.class); + when(modal.getId()).thenReturn(id); + when(modal.getTitle()).thenReturn(title); + + return modal; + } + + private static void mockReplyCallbacks(IReplyCallback event, MessageChannel channel, long id) { when(event.reply(anyString())).thenAnswer(invocation -> mockReply(mockMessage(invocation.getArgument(0), channel, id))); when(event.reply(any(MessageCreateData.class))).thenAnswer(invocation -> @@ -562,10 +623,6 @@ public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, embeds.add(invocation.getArgument(0)); return mockReply(mockMessage(null, embeds, channel, id)); }); - when(event.replyModal(any(Modal.class))).thenAnswer(inv -> { - CURRENT_MODAL = inv.getArgument(0); - return mockModalReply(user, channel); - }); when(event.deferReply()).thenAnswer(inv -> { return mockReply(mockMessage(null, channel, id)); @@ -573,8 +630,6 @@ public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, when(event.deferReply(anyBoolean())).thenAnswer(inv -> { return mockReply(mockMessage(null, channel, id)); }); - - return event; } private static ReplyCallbackAction mockReply(Message message) { @@ -668,6 +723,18 @@ private static & RestAction> T mockReply(Class { + Consumer messageConsumer = inv.getArgument(0); + Consumer errorConsumer = inv.getArgument(1); + + try { + messageConsumer.accept(message); + } catch (Exception e) { + errorConsumer.accept(e); + } + return null; + }).when(action).queue(any(), any()); + when(action.getEmbeds()).thenAnswer(inv -> message.getEmbeds()); when(action.setEmbeds(anyCollection())).thenAnswer(inv -> { Collection embed = inv.getArgument(0); @@ -675,6 +742,9 @@ private static & RestAction> T mockReply(Class action); + when(action.setActionRow(anyCollection())).thenAnswer(inv -> action); + return action; } diff --git a/src/test/java/io/codemc/bot/listeners/TestModalListener.java b/src/test/java/io/codemc/bot/listeners/TestModalListener.java new file mode 100644 index 0000000..c3fedd9 --- /dev/null +++ b/src/test/java/io/codemc/bot/listeners/TestModalListener.java @@ -0,0 +1,132 @@ +package io.codemc.bot.listeners; + +import static io.codemc.bot.MockJDA.GENERAL; +import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.api.jenkins.JenkinsAPI; +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import io.codemc.bot.utils.CommandUtil; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.interactions.modals.Modal; + +public class TestModalListener { + + private static ModalListener listener; + + @BeforeAll + public static void init() { + listener = new ModalListener(MockCodeMCBot.INSTANCE); + } + + + @Test + @DisplayName("Test Submit") + public void testSubmit() { + String username = "TestModalListenerSubmit"; + Modal modal = MockJDA.mockModal("submit", "Submit"); + Map options = Map.of( + "user", username, + "repo", "Job", + "description", "description", + "repoLink", "repoLink" + ); + + MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, options, CommandUtil.embedSuccess("[Request sent!]()")); + MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, Map.of("user", username, "repo", "Job", "description", "description"), CommandUtil.embedSuccess("[Request sent!]()")); + + MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, Map.of(), CommandUtil.embedError("The provided user was invalid.")); + + JenkinsAPI.createJenkinsUser(username, "1234"); + MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, options, CommandUtil.embedError("A Jenkins User named '" + username + "' already exists!")); + JenkinsAPI.deleteUser(username); + + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username, "description", "Description"), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username, "repo", "Job"), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username, "repo", "", "description", "Description"), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username, "repo", "Job", "description", ""), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + } + + @Test + @DisplayName("Test Deny Application") + public void testDenyApplication() { + String username = "TestModalApplicationDeny"; + Member member = MockJDA.mockMember(username); + + MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); + + Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); + Message message2 = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); + + Modal m1 = MockJDA.mockModal("deny_application:" + message.getId(), "Deny Application"); + Modal m2 = MockJDA.mockModal("deny_application:" + message2.getId(), "Deny Application"); + + long id1 = MockJDA.assertModalInteractionEvent(listener, m1, REQUEST_CHANNEL, Map.of(), (MessageEmbed[]) null); + long id2 = MockJDA.assertModalInteractionEvent(listener, m2, REQUEST_CHANNEL, Map.of("reason", "reason"), (MessageEmbed[]) null); + + String expected = String.format(""" + [<:like:935126958193405962>] Handling of Join Request complete! + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `rejected-requests` channel found! + - [<:like:935126958193405962>] Join Request removed! + - Thread archived! + - Request Message deleted! + - [<:like:935126958193405962>] Finished rejecting join request of %s! + """, member.getId(), member.getUser().getEffectiveName()); + + assertEquals(expected, MockJDA.getMessage(id1)); + assertEquals(expected, MockJDA.getMessage(id2)); + + Modal m3 = MockJDA.mockModal("deny_application", "Deny Application"); + MockJDA.assertModalInteractionEvent(listener, m3, REQUEST_CHANNEL, Map.of(), CommandUtil.embedError("Received invalid Deny Application modal!")); + + Modal m4 = MockJDA.mockModal("deny_application:abcd", "Deny Application"); + MockJDA.assertModalInteractionEvent(listener, m4, REQUEST_CHANNEL, Map.of(), CommandUtil.embedError("Received invalid message ID: abcd")); + } + + @Test + @DisplayName("Test ModalListener Errors") + public void testModalListenerErrors() { + Modal modal = MockJDA.mockModal("null", "null"); + + ModalInteractionEvent e1 = MockJDA.mockModalInteractionEvent(modal, REQUEST_CHANNEL, Map.of()); + when(e1.getGuild()).thenReturn(null); + MockJDA.assertModalInteractionEvent(listener, e1, CommandUtil.embedError("Unable to retrieve CodeMC Server!")); + + ModalInteractionEvent e2 = MockJDA.mockModalInteractionEvent(modal, REQUEST_CHANNEL, Map.of()); + MockJDA.assertModalInteractionEvent(listener, e2, CommandUtil.embedError("Received Modal with unknown ID `null`.")); + } + +}