From 7a0cd006832b23d8c9617a9babecbab6d8e89f0c Mon Sep 17 00:00:00 2001 From: Jackson Bailey Date: Sat, 29 Jun 2024 01:03:52 -0400 Subject: [PATCH] Move common Command logic to AbstractCommand --- .../wheel/terra/config/TerraConfig.java | 9 +- .../wheel/terra/service/AbstractCommand.java | 30 +++++- .../{EchoListener.java => EchoCommand.java} | 17 +++- .../wheel/terra/service/Foo.java | 39 -------- .../jacksonbailey/wheel/terra/TestUtils.java | 20 ++++ .../wheel/terra/config/TerraTestConfig.java | 11 ++- .../terra/service/AbstractCommandTest.java | 99 +++++++++++++++++++ .../wheel/terra/service/EchoCommandTest.java | 38 +++++++ 8 files changed, 211 insertions(+), 52 deletions(-) rename terra/src/main/java/dev/jacksonbailey/wheel/terra/service/{EchoListener.java => EchoCommand.java} (61%) delete mode 100644 terra/src/main/java/dev/jacksonbailey/wheel/terra/service/Foo.java create mode 100644 terra/src/test/java/dev/jacksonbailey/wheel/terra/TestUtils.java create mode 100644 terra/src/test/java/dev/jacksonbailey/wheel/terra/service/AbstractCommandTest.java create mode 100644 terra/src/test/java/dev/jacksonbailey/wheel/terra/service/EchoCommandTest.java diff --git a/terra/src/main/java/dev/jacksonbailey/wheel/terra/config/TerraConfig.java b/terra/src/main/java/dev/jacksonbailey/wheel/terra/config/TerraConfig.java index 9f7de81..59e168b 100644 --- a/terra/src/main/java/dev/jacksonbailey/wheel/terra/config/TerraConfig.java +++ b/terra/src/main/java/dev/jacksonbailey/wheel/terra/config/TerraConfig.java @@ -5,10 +5,8 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.hooks.EventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -22,11 +20,8 @@ public class TerraConfig { @Bean @Scope("prototype") - public JDABuilder jdaBuilder(TerraConfigProps terraConfigProps, - ObjectProvider listenerProvider) { - JDABuilder builder = JDABuilder.createDefault(terraConfigProps.token()); - listenerProvider.orderedStream().forEach(builder::addEventListeners); - return builder; + public JDABuilder jdaBuilder(TerraConfigProps terraConfigProps) { + return JDABuilder.createDefault(terraConfigProps.token()); } @Bean diff --git a/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/AbstractCommand.java b/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/AbstractCommand.java index 622e1ed..98ecf27 100644 --- a/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/AbstractCommand.java +++ b/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/AbstractCommand.java @@ -1,21 +1,36 @@ package dev.jacksonbailey.wheel.terra.service; +import jakarta.annotation.PostConstruct; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.function.Consumer; +import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public abstract class AbstractCommand extends ListenerAdapter { - private static final Logger log = LoggerFactory.getLogger(AbstractCommand.class); + protected final JDA jda; + + public AbstractCommand(JDA jda) { + this.jda = jda; + } + + public abstract CommandData getCommandData(); + + @PostConstruct + public void registerCommand() { + jda.addEventListener(this); + jda.upsertCommand(getCommandData()) + .queue(this::onUpsertCommandSuccess, onUpsertCommandFailure()); + } public abstract MessageCreateData doCommand(@NotNull SlashCommandInteractionEvent event); @@ -32,6 +47,15 @@ public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent even } } + protected void onUpsertCommandSuccess(Command command) { + + } + + // It seems like JDA logs errors by default + protected Consumer onUpsertCommandFailure() { + return null; + } + protected void onDeferReplySuccess(InteractionHook hook, Future messageFuture) { MessageCreateData messageCreateData; diff --git a/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/EchoListener.java b/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/EchoCommand.java similarity index 61% rename from terra/src/main/java/dev/jacksonbailey/wheel/terra/service/EchoListener.java rename to terra/src/main/java/dev/jacksonbailey/wheel/terra/service/EchoCommand.java index 57bd7d3..2d73bbf 100644 --- a/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/EchoListener.java +++ b/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/EchoCommand.java @@ -1,18 +1,31 @@ package dev.jacksonbailey.wheel.terra.service; -import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; @Service -public class EchoListener extends AbstractCommand { +public class EchoCommand extends AbstractCommand { public static final String ECHO_COMMAND_NAME = "echo"; public static final String ECHO_PARAM_NAME = "message"; + public EchoCommand(JDA jda) { + super(jda); + } + + @Override + public CommandData getCommandData() { + return Commands.slash(ECHO_COMMAND_NAME, "Says it right back") + .addOption(OptionType.STRING, ECHO_PARAM_NAME, "The thing to say"); + } + @Override public MessageCreateData doCommand(@NotNull SlashCommandInteractionEvent event) { var builder = new MessageCreateBuilder(); diff --git a/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/Foo.java b/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/Foo.java deleted file mode 100644 index 790e2b8..0000000 --- a/terra/src/main/java/dev/jacksonbailey/wheel/terra/service/Foo.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.jacksonbailey.wheel.terra.service; - -import static dev.jacksonbailey.wheel.terra.service.EchoListener.ECHO_COMMAND_NAME; -import static dev.jacksonbailey.wheel.terra.service.EchoListener.ECHO_PARAM_NAME; - -import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.Commands; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.annotation.Profile; -import org.springframework.context.event.EventListener; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; - -@Component -@Profile("live") -public class Foo { - - private static final Logger log = LoggerFactory.getLogger(Foo.class); - - private final JDA jda; - - public Foo(JDA jda) { - this.jda = jda; - } - - @Async - @EventListener(ApplicationReadyEvent.class) - public void doStuff() { - log.info("Here we go!"); - jda.upsertCommand( - Commands.slash(ECHO_COMMAND_NAME, "Says it right back") - .addOption(OptionType.STRING, ECHO_PARAM_NAME, "The thing to say") - ).queue(); - } - -} diff --git a/terra/src/test/java/dev/jacksonbailey/wheel/terra/TestUtils.java b/terra/src/test/java/dev/jacksonbailey/wheel/terra/TestUtils.java new file mode 100644 index 0000000..71af006 --- /dev/null +++ b/terra/src/test/java/dev/jacksonbailey/wheel/terra/TestUtils.java @@ -0,0 +1,20 @@ +package dev.jacksonbailey.wheel.terra; + +import static org.mockito.ArgumentMatchers.any; + +import java.util.function.Consumer; + +public final class TestUtils { + + private TestUtils() { + throw new AssertionError(); + } + + public static Consumer anyConsumerOf(T t) { + @SuppressWarnings("unchecked") + Consumer toReturn = any(Consumer.class); + return toReturn; + } + + +} diff --git a/terra/src/test/java/dev/jacksonbailey/wheel/terra/config/TerraTestConfig.java b/terra/src/test/java/dev/jacksonbailey/wheel/terra/config/TerraTestConfig.java index e08b9a9..a7272a2 100644 --- a/terra/src/test/java/dev/jacksonbailey/wheel/terra/config/TerraTestConfig.java +++ b/terra/src/test/java/dev/jacksonbailey/wheel/terra/config/TerraTestConfig.java @@ -1,8 +1,13 @@ package dev.jacksonbailey.wheel.terra.config; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.requests.RestAction; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -13,7 +18,11 @@ public class TerraTestConfig { @Bean @Profile("!live") public JDA jda() { - return mock(JDA.class); + JDA jda = mock(JDA.class); + @SuppressWarnings("unchecked") + RestAction restAction = mock(RestAction.class); + given(jda.upsertCommand(any(CommandData.class))).willReturn(restAction); + return jda; } } diff --git a/terra/src/test/java/dev/jacksonbailey/wheel/terra/service/AbstractCommandTest.java b/terra/src/test/java/dev/jacksonbailey/wheel/terra/service/AbstractCommandTest.java new file mode 100644 index 0000000..2c77d9f --- /dev/null +++ b/terra/src/test/java/dev/jacksonbailey/wheel/terra/service/AbstractCommandTest.java @@ -0,0 +1,99 @@ +package dev.jacksonbailey.wheel.terra.service; + +import static dev.jacksonbailey.wheel.terra.TestUtils.anyConsumerOf; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +import java.util.function.Consumer; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class AbstractCommandTest { + + @Mock + private CommandData commandData; + + @Mock + private JDA jda; + + @InjectMocks + private AbstractCommandStub abstractCommandStub; + + @Mock + RestAction upsertRestAction; + + @Mock + Consumer upsertCommandFailure; + + private static class AbstractCommandStub extends AbstractCommand { + + private final CommandData commandData; + + private final Consumer upsertCommandFailure; + + public AbstractCommandStub(JDA jda, CommandData commandData, + Consumer upsertCommandFailure) { + super(jda); + this.commandData = commandData; + this.upsertCommandFailure = upsertCommandFailure; + } + + @Override + public CommandData getCommandData() { + return commandData; + } + + @Override + public MessageCreateData doCommand(@NotNull SlashCommandInteractionEvent event) { + return null; + } + + @Override + public String getCommandName() { + return "stub"; + } + + @Override + public boolean isEphemeral() { + return false; + } + + protected Consumer onUpsertCommandFailure() { + return upsertCommandFailure; + } + } + + @BeforeEach + void beforeEach() { + given(jda.upsertCommand(any(CommandData.class))).willReturn(upsertRestAction); + } + + @Test + void registerCommandAddsListener() { + abstractCommandStub.registerCommand(); + verify(jda).addEventListener(abstractCommandStub); + } + + @Test + void registerCommandUpsertsCommand() { + abstractCommandStub.registerCommand(); + verify(jda).upsertCommand(commandData); + verify(upsertRestAction).queue(anyConsumerOf(Command.class), eq(upsertCommandFailure)); + } + + // TODO Add more unit tests +} diff --git a/terra/src/test/java/dev/jacksonbailey/wheel/terra/service/EchoCommandTest.java b/terra/src/test/java/dev/jacksonbailey/wheel/terra/service/EchoCommandTest.java new file mode 100644 index 0000000..b1c208d --- /dev/null +++ b/terra/src/test/java/dev/jacksonbailey/wheel/terra/service/EchoCommandTest.java @@ -0,0 +1,38 @@ +package dev.jacksonbailey.wheel.terra.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class EchoCommandTest { + + @Mock + private JDA jda; + + @InjectMocks + private EchoCommand echoCommand; + + @Test + void echoReturnsInput() { + var event = mock(SlashCommandInteractionEvent.class); + var optionMapping = mock(OptionMapping.class); + given(event.getOption(EchoCommand.ECHO_PARAM_NAME)).willReturn(optionMapping); + var input = "Hello, World!"; + given(optionMapping.getAsString()).willReturn(input); + + try (var messageCreateData = echoCommand.doCommand(event)) { + assertEquals(input, messageCreateData.getContent()); + } + } + +}