Skip to content

Commit

Permalink
Create TestModalListener.java
Browse files Browse the repository at this point in the history
  • Loading branch information
gmitch215 authored Dec 7, 2024
1 parent f6b8197 commit 73069dc
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 29 deletions.
128 changes: 99 additions & 29 deletions src/test/java/io/codemc/bot/MockJDA.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<TextChannel> CHANNELS = List.of(REQUEST_CHANNEL, ACCEPTED_CHANNEL, REJECTED_CHANNEL);
public static final List<TextChannel> 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);
Expand Down Expand Up @@ -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("<Jump URL>");
when(message.getIdLong()).thenReturn(id);
when(message.getId()).thenReturn(Long.toString(id));
when(message.getGuild()).thenReturn(GUILD);
Expand All @@ -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;
}
Expand Down Expand Up @@ -422,7 +433,7 @@ public static void assertEmbed(MessageEmbed embed, MessageEmbed expectedOutput,

public static long assertSlashCommandEvent(TestCommandListener listener, Map<String, Object> 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);
}

Expand Down Expand Up @@ -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<MessageEmbed> 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);
});
Expand All @@ -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);
Expand All @@ -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<String, String> 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<String, String> 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 ->
Expand All @@ -562,19 +623,13 @@ 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));
});
when(event.deferReply(anyBoolean())).thenAnswer(inv -> {
return mockReply(mockMessage(null, channel, id));
});

return event;
}

private static ReplyCallbackAction mockReply(Message message) {
Expand Down Expand Up @@ -668,13 +723,28 @@ private static <T extends MessageRequest<?> & RestAction<?>> T mockReply(Class<T
return null;
}).when(action).queue(any());

doAnswer(inv -> {
Consumer<Message> messageConsumer = inv.getArgument(0);
Consumer<Throwable> 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<MessageEmbed> embed = inv.getArgument(0);
embeds.put(message.getIdLong(), embed.toArray(new MessageEmbed[0]));
return action;
});

when(action.setActionRow(any(ItemComponent[].class))).thenAnswer(inv -> action);
when(action.setActionRow(anyCollection())).thenAnswer(inv -> action);

return action;
}

Expand Down
132 changes: 132 additions & 0 deletions src/test/java/io/codemc/bot/listeners/TestModalListener.java
Original file line number Diff line number Diff line change
@@ -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<String, String> options = Map.of(
"user", username,
"repo", "Job",
"description", "description",
"repoLink", "repoLink"
);

MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, options, CommandUtil.embedSuccess("[Request sent!](<Jump URL>)"));
MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, Map.of("user", username, "repo", "Job", "description", "description"), CommandUtil.embedSuccess("[Request sent!](<Jump URL>)"));

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`."));
}

}

0 comments on commit 73069dc

Please sign in to comment.