Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7443cbfb4b | ||
|
|
f96c2677b6 | ||
|
|
8e4e77cd31 |
No files matched your search
@@ -12,7 +12,7 @@ import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
|
||||
|
||||
public interface Command {
|
||||
enum Type {
|
||||
SLASH, USER, MESSAGE
|
||||
CHAT, USER, MESSAGE
|
||||
}
|
||||
|
||||
boolean needAdmin();
|
||||
|
||||
@@ -5,7 +5,6 @@ package me.bobert.netneutrality.core.command;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
@@ -13,25 +12,47 @@ import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData;
|
||||
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class CommandContainer {
|
||||
public static final CommandContainer NULL_COMMAND = new CommandContainer(null);
|
||||
private static final CommandUsageFormatter USAGE_FORMATTER = new CommandUsageFormatter();
|
||||
private final Command command;
|
||||
private CommandDataImpl commandData;
|
||||
private List<OptionData> options;
|
||||
private List<SubcommandData> subcommands;
|
||||
private List<SubcommandGroupData> subcommandGroups;
|
||||
private final CommandDataImpl commandData;
|
||||
private final List<OptionData> options;
|
||||
private final List<SubcommandData> subcommands;
|
||||
private final List<SubcommandGroupData> subcommandGroups;
|
||||
|
||||
public CommandContainer(Command command) {
|
||||
this.command = command;
|
||||
|
||||
if (command != null) {
|
||||
CommandOptionsBuilder commandOptionsBuilder = new CommandOptionsBuilder(command.getType(), command);
|
||||
command.registerCommandOptions(commandOptionsBuilder);
|
||||
initContainer(commandOptionsBuilder);
|
||||
this(command, build(command));
|
||||
}
|
||||
|
||||
private CommandContainer(Command command, CommandDataImpl commandData) {
|
||||
this.command = command;
|
||||
this.commandData = commandData;
|
||||
|
||||
if (commandData != null) {
|
||||
this.options = commandData.getOptions();
|
||||
this.subcommands = commandData.getSubcommands();
|
||||
this.subcommandGroups = commandData.getSubcommandGroups();
|
||||
} else {
|
||||
this.options = List.of();
|
||||
this.subcommands = List.of();
|
||||
this.subcommandGroups = List.of();
|
||||
}
|
||||
}
|
||||
|
||||
public static CommandContainer of(Command command) {
|
||||
return new CommandContainer(command);
|
||||
}
|
||||
|
||||
private static CommandDataImpl build(Command command) {
|
||||
if (command == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CommandOptionsBuilder builder = new CommandOptionsBuilder(command.getType(), command);
|
||||
command.registerCommandOptions(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public Command getCommand() {
|
||||
@@ -54,129 +75,11 @@ public class CommandContainer {
|
||||
return subcommandGroups;
|
||||
}
|
||||
|
||||
private void initContainer(CommandOptionsBuilder builder) {
|
||||
this.commandData = builder.build();
|
||||
this.options = commandData.getOptions();
|
||||
this.subcommands = commandData.getSubcommands();
|
||||
this.subcommandGroups = commandData.getSubcommandGroups();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return options.isEmpty() && subcommands.isEmpty() && subcommandGroups.isEmpty();
|
||||
}
|
||||
|
||||
public String printUsage() {
|
||||
StringBuilder usage = new StringBuilder();
|
||||
if (!options.isEmpty()) {
|
||||
usage.append("- Usage: /").append(command.getCommandName());
|
||||
for (OptionData option : options) {
|
||||
usage.append(" <").append(option.getName()).append(">");
|
||||
}
|
||||
|
||||
usage.append(printOptions(options, null, false));
|
||||
}
|
||||
|
||||
if (!subcommands.isEmpty()) {
|
||||
for (SubcommandData subcommand : subcommands) {
|
||||
usage.append("- Usage: /").append(command.getCommandName()).append(" ").append(subcommand.getName());
|
||||
|
||||
for (OptionData option : subcommand.getOptions()) {
|
||||
usage.append(" <").append(option.getName()).append(">");
|
||||
}
|
||||
|
||||
usage.append(" (").append(subcommand.getDescription()).append(")");
|
||||
usage.append(printOptions(subcommand.getOptions(), null, false));
|
||||
|
||||
usage.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!subcommandGroups.isEmpty()) {
|
||||
for (SubcommandGroupData subcommandGroup : subcommandGroups) {
|
||||
usage.append("- Subcommand: /").append(command.getCommandName())
|
||||
.append(" ").append(subcommandGroup.getName())
|
||||
.append(" (").append(subcommandGroup.getDescription()).append(")")
|
||||
.append("\n");
|
||||
|
||||
for (SubcommandData subcommand : subcommandGroup.getSubcommands()) {
|
||||
usage.append(" - Usage: /").append(command.getCommandName())
|
||||
.append(" ").append(subcommandGroup.getName())
|
||||
.append(" ").append(subcommand.getName());
|
||||
|
||||
for (OptionData option : subcommand.getOptions()) {
|
||||
usage.append(" <").append(option.getName()).append(">");
|
||||
}
|
||||
|
||||
usage.append(printOptions(subcommand.getOptions(), subcommand.getDescription(), true));
|
||||
|
||||
usage.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEmpty()) {
|
||||
usage.append("\n\n-# *: required argument");
|
||||
}
|
||||
|
||||
return usage.toString();
|
||||
}
|
||||
|
||||
private String printOptions(List<OptionData> options, String description, boolean group) {
|
||||
StringBuilder usage = new StringBuilder();
|
||||
|
||||
if (description != null) {
|
||||
usage.append(" (").append(description).append(")");
|
||||
}
|
||||
|
||||
for (OptionData option : options) {
|
||||
usage.append("\n");
|
||||
if (group) {
|
||||
usage.append(" ");
|
||||
}
|
||||
|
||||
usage.append(" - ");
|
||||
if (option.isRequired()) {
|
||||
usage.append("*");
|
||||
}
|
||||
usage.append(option.getName()).append(": ").append(option.getDescription());
|
||||
|
||||
if (option.getMinValue() != null) {
|
||||
usage.append(" [Min value: ").append(option.getMinValue().longValue()).append("]");
|
||||
}
|
||||
|
||||
if (option.getMaxValue() != null) {
|
||||
usage.append(" [Max value: ").append(option.getMaxValue().longValue()).append("]");
|
||||
}
|
||||
|
||||
if (option.getMinLength() != null) {
|
||||
usage.append(" [Min length: ").append(option.getMinLength().longValue()).append("]");
|
||||
}
|
||||
|
||||
if (option.getMaxLength() != null) {
|
||||
usage.append(" [Max length: ").append(option.getMaxLength().longValue()).append("]");
|
||||
}
|
||||
|
||||
List<net.dv8tion.jda.api.interactions.commands.Command.Choice> choices = option.getChoices();
|
||||
if (!choices.isEmpty()) {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (net.dv8tion.jda.api.interactions.commands.Command.Choice choice : choices) {
|
||||
joiner.add(choice.getAsString());
|
||||
}
|
||||
|
||||
usage.append(" [Available choices: ").append(joiner).append("]");
|
||||
}
|
||||
|
||||
Set<ChannelType> requiredChannelTypes = option.getChannelTypes();
|
||||
if (!requiredChannelTypes.isEmpty()) {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (ChannelType type : requiredChannelTypes) {
|
||||
joiner.add(type.name());
|
||||
}
|
||||
|
||||
usage.append(" [Required channel types: ").append(joiner).append("]");
|
||||
}
|
||||
}
|
||||
|
||||
return usage.toString();
|
||||
return USAGE_FORMATTER.format(this);
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public class CommandManager implements CommandRegistry {
|
||||
public void init() {
|
||||
logger.info("Loading commands..");
|
||||
|
||||
initCommands();
|
||||
ReflectionHelper.loadCommands(Command.class, context, this::onCommandLoaded);
|
||||
|
||||
logger.info("Loaded {} commands!", commands.size());
|
||||
}
|
||||
@@ -84,8 +84,7 @@ public class CommandManager implements CommandRegistry {
|
||||
return commands;
|
||||
}
|
||||
|
||||
public void initCommands() {
|
||||
Consumer<Command> onLoaded = command -> {
|
||||
void onCommandLoaded(Command command) {
|
||||
if (!command.isEnabled()) {
|
||||
logger.info("Skipping command {} as it is disabled", command.getClass().getName());
|
||||
return;
|
||||
@@ -96,7 +95,7 @@ public class CommandManager implements CommandRegistry {
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.getDescription() == null && command.getType() == Command.Type.SLASH) {
|
||||
if (command.getDescription() == null && command.getType() == Command.Type.CHAT) {
|
||||
logger.error("Failed to load command {}: Command description is null", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
@@ -105,9 +104,6 @@ public class CommandManager implements CommandRegistry {
|
||||
if (!commands.containsKey(command.getCommandName())) {
|
||||
commands.put(command.getCommandName(), container);
|
||||
}
|
||||
};
|
||||
|
||||
ReflectionHelper.loadCommands(Command.class, context, onLoaded);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,7 +31,7 @@ public class CommandOptionsBuilder implements SlashCommandData {
|
||||
|
||||
public CommandOptionsBuilder(Command.Type type, Command command) {
|
||||
|
||||
if (type == Command.Type.SLASH) {
|
||||
if (type == Command.Type.CHAT) {
|
||||
commandData = (CommandDataImpl) Commands.slash(command.getCommandName(), command.getDescription());
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public class CommandProcessor {
|
||||
logger.info("Executing command {} on guild {}", command.getCommandName(), context.getGuild() == null ? "PrivateMessage" : context.getGuild().getId());
|
||||
configureSentry(command);
|
||||
|
||||
if (command.getType() == Command.Type.SLASH && !usageValidator.validate(context, container)) {
|
||||
if (command.getType() == Command.Type.CHAT && !usageValidator.validate(context, container)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package me.bobert.netneutrality.core.command;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
final class CommandUsageFormatter {
|
||||
private static final String REQUIRED_LEGEND = "\n\n-# *: required argument";
|
||||
|
||||
String format(CommandContainer container) {
|
||||
if (container.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String commandName = container.getCommand().getCommandName();
|
||||
StringBuilder usage = new StringBuilder();
|
||||
appendOptions(usage, commandName, container.getOptions());
|
||||
appendSubcommands(usage, commandName, container.getSubcommands());
|
||||
appendGroups(usage, commandName, container.getSubcommandGroups());
|
||||
usage.append(REQUIRED_LEGEND);
|
||||
|
||||
return usage.toString();
|
||||
}
|
||||
|
||||
private void appendOptions(StringBuilder usage, String commandName, List<OptionData> options) {
|
||||
if (options.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
usage.append("- Usage: /").append(commandName);
|
||||
appendArgs(usage, options);
|
||||
usage.append(describeOptions(options, null, false));
|
||||
}
|
||||
|
||||
private void appendSubcommands(StringBuilder usage, String commandName, List<SubcommandData> subcommands) {
|
||||
for (SubcommandData subcommand : subcommands) {
|
||||
usage.append("- Usage: /").append(commandName).append(" ").append(subcommand.getName());
|
||||
appendArgs(usage, subcommand.getOptions());
|
||||
usage.append(" (").append(subcommand.getDescription()).append(")");
|
||||
usage.append(describeOptions(subcommand.getOptions(), null, false));
|
||||
usage.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
private void appendGroups(StringBuilder usage, String commandName, List<SubcommandGroupData> groups) {
|
||||
for (SubcommandGroupData group : groups) {
|
||||
usage.append("- Subcommand: /").append(commandName)
|
||||
.append(" ").append(group.getName())
|
||||
.append(" (").append(group.getDescription()).append(")")
|
||||
.append("\n");
|
||||
|
||||
for (SubcommandData subcommand : group.getSubcommands()) {
|
||||
usage.append(" - Usage: /").append(commandName)
|
||||
.append(" ").append(group.getName())
|
||||
.append(" ").append(subcommand.getName());
|
||||
appendArgs(usage, subcommand.getOptions());
|
||||
usage.append(describeOptions(subcommand.getOptions(), subcommand.getDescription(), true));
|
||||
usage.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void appendArgs(StringBuilder usage, List<OptionData> options) {
|
||||
for (OptionData option : options) {
|
||||
usage.append(" <").append(option.getName()).append(">");
|
||||
}
|
||||
}
|
||||
|
||||
private String describeOptions(List<OptionData> options, String description, boolean group) {
|
||||
StringBuilder usage = new StringBuilder();
|
||||
|
||||
if (description != null) {
|
||||
usage.append(" (").append(description).append(")");
|
||||
}
|
||||
|
||||
for (OptionData option : options) {
|
||||
usage.append("\n");
|
||||
if (group) {
|
||||
usage.append(" ");
|
||||
}
|
||||
|
||||
usage.append(" - ");
|
||||
if (option.isRequired()) {
|
||||
usage.append("*");
|
||||
}
|
||||
usage.append(option.getName()).append(": ").append(option.getDescription());
|
||||
|
||||
appendConstraints(usage, option);
|
||||
appendChoices(usage, option);
|
||||
appendChannelTypes(usage, option);
|
||||
}
|
||||
|
||||
return usage.toString();
|
||||
}
|
||||
|
||||
private void appendConstraints(StringBuilder usage, OptionData option) {
|
||||
if (option.getMinValue() != null) {
|
||||
usage.append(" [Min value: ").append(option.getMinValue().longValue()).append("]");
|
||||
}
|
||||
|
||||
if (option.getMaxValue() != null) {
|
||||
usage.append(" [Max value: ").append(option.getMaxValue().longValue()).append("]");
|
||||
}
|
||||
|
||||
if (option.getMinLength() != null) {
|
||||
usage.append(" [Min length: ").append(option.getMinLength().longValue()).append("]");
|
||||
}
|
||||
|
||||
if (option.getMaxLength() != null) {
|
||||
usage.append(" [Max length: ").append(option.getMaxLength().longValue()).append("]");
|
||||
}
|
||||
}
|
||||
|
||||
private void appendChoices(StringBuilder usage, OptionData option) {
|
||||
List<net.dv8tion.jda.api.interactions.commands.Command.Choice> choices = option.getChoices();
|
||||
if (choices.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (net.dv8tion.jda.api.interactions.commands.Command.Choice choice : choices) {
|
||||
joiner.add(choice.getAsString());
|
||||
}
|
||||
|
||||
usage.append(" [Available choices: ").append(joiner).append("]");
|
||||
}
|
||||
|
||||
private void appendChannelTypes(StringBuilder usage, OptionData option) {
|
||||
Set<ChannelType> types = option.getChannelTypes();
|
||||
if (types.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (ChannelType type : types) {
|
||||
joiner.add(type.name());
|
||||
}
|
||||
|
||||
usage.append(" [Required channel types: ").append(joiner).append("]");
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public abstract class AbstractCommand implements Command {
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.SLASH;
|
||||
return Type.CHAT;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ public class PermissionPrecondition implements CommandPrecondition {
|
||||
|
||||
@Override
|
||||
public boolean check(CommandContext context, Command command) {
|
||||
if (command.getType() != Command.Type.SLASH) {
|
||||
if (command.getType() != Command.Type.CHAT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
package me.bobert.netneutrality.core.command;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class CommandContainerTest {
|
||||
|
||||
@Mock
|
||||
private Command command;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(command.getCommandName()).thenReturn("test");
|
||||
when(command.getDescription()).thenReturn("A test command");
|
||||
when(command.getType()).thenReturn(Command.Type.CHAT);
|
||||
}
|
||||
|
||||
private CommandContainer container(Consumer<CommandOptionsBuilder> registration) {
|
||||
doAnswer(invocation -> {
|
||||
registration.accept(invocation.getArgument(0));
|
||||
return null;
|
||||
}).when(command).registerCommandOptions(org.mockito.ArgumentMatchers.any());
|
||||
|
||||
return new CommandContainer(command);
|
||||
}
|
||||
|
||||
// ===== NULL_COMMAND and construction =====
|
||||
|
||||
@Test
|
||||
void nullCommandShouldHaveNoCommand() {
|
||||
assertNull(CommandContainer.NULL_COMMAND.getCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullCommandShouldBeEmptyAndPrintNothing() {
|
||||
assertTrue(CommandContainer.NULL_COMMAND.isEmpty());
|
||||
assertEquals("", CommandContainer.NULL_COMMAND.printUsage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofShouldBuildTheSameAsTheConstructor() {
|
||||
doAnswer(invocation -> {
|
||||
((CommandOptionsBuilder) invocation.getArgument(0))
|
||||
.addOptions(new OptionData(OptionType.STRING, "text", "Text"));
|
||||
return null;
|
||||
}).when(command).registerCommandOptions(org.mockito.ArgumentMatchers.any());
|
||||
|
||||
CommandContainer container = CommandContainer.of(command);
|
||||
|
||||
assertSame(command, container.getCommand());
|
||||
assertEquals(1, container.getOptions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeTheCommand() {
|
||||
assertSame(command, container(_ -> { }).getCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeTheBuiltSlashData() {
|
||||
CommandContainer container = container(_ -> { });
|
||||
|
||||
assertNotNull(container.getSlashCommandData());
|
||||
assertEquals("test", container.getSlashCommandData().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeTheRegisteredOptionsSubcommandsAndGroups() {
|
||||
CommandContainer container = container(builder -> {
|
||||
builder.addOptions(new OptionData(OptionType.STRING, "text", "Text"));
|
||||
});
|
||||
|
||||
assertEquals(1, container.getOptions().size());
|
||||
assertTrue(container.getSubcommands().isEmpty());
|
||||
assertTrue(container.getSubcommandGroups().isEmpty());
|
||||
}
|
||||
|
||||
// ===== isEmpty =====
|
||||
|
||||
@Test
|
||||
void shouldBeEmptyWithoutAnyOptions() {
|
||||
assertTrue(container(_ -> { }).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotBeEmptyWithOptions() {
|
||||
assertFalse(container(builder -> builder.addOptions(new OptionData(OptionType.STRING, "text", "Text"))).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotBeEmptyWithSubcommands() {
|
||||
assertFalse(container(builder -> builder.addSubcommands(new SubcommandData("add", "Adds"))).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotBeEmptyWithSubcommandGroups() {
|
||||
CommandContainer container = container(builder -> builder.addSubcommandGroups(
|
||||
new SubcommandGroupData("grp", "Group").addSubcommands(new SubcommandData("add", "Adds"))));
|
||||
|
||||
assertFalse(container.isEmpty());
|
||||
}
|
||||
|
||||
// ===== printUsage =====
|
||||
|
||||
@Test
|
||||
void shouldPrintNothingWithoutOptions() {
|
||||
assertEquals("", container(_ -> { }).printUsage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintPlainOptions() {
|
||||
CommandContainer container = container(builder -> builder.addOptions(
|
||||
new OptionData(OptionType.INTEGER, "amount", "Amount", true),
|
||||
new OptionData(OptionType.STRING, "reason", "Reason")));
|
||||
|
||||
assertEquals("- Usage: /test <amount> <reason>\n"
|
||||
+ " - *amount: Amount\n"
|
||||
+ " - reason: Reason\n\n"
|
||||
+ "-# *: required argument", container.printUsage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintMinAndMaxValues() {
|
||||
CommandContainer container = container(builder -> builder.addOptions(
|
||||
new OptionData(OptionType.INTEGER, "amount", "Amount").setMinValue(1).setMaxValue(10)));
|
||||
|
||||
String usage = container.printUsage();
|
||||
|
||||
assertTrue(usage.contains("amount: Amount [Min value: 1] [Max value: 10]"), usage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintMinAndMaxLengths() {
|
||||
CommandContainer container = container(builder -> builder.addOptions(
|
||||
new OptionData(OptionType.STRING, "text", "Text").setMinLength(2).setMaxLength(20)));
|
||||
|
||||
String usage = container.printUsage();
|
||||
|
||||
assertTrue(usage.contains("text: Text [Min length: 2] [Max length: 20]"), usage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintChoices() {
|
||||
CommandContainer container = container(builder -> builder.addOptions(
|
||||
new OptionData(OptionType.STRING, "mode", "Mode").addChoice("One", "one").addChoice("Two", "two")));
|
||||
|
||||
String usage = container.printUsage();
|
||||
|
||||
assertTrue(usage.contains("[Available choices: one, two]"), usage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintRequiredChannelTypes() {
|
||||
CommandContainer container = container(builder -> builder.addOptions(
|
||||
new OptionData(OptionType.CHANNEL, "channel", "Channel").setChannelTypes(ChannelType.TEXT)));
|
||||
|
||||
String usage = container.printUsage();
|
||||
|
||||
assertTrue(usage.contains("[Required channel types: TEXT]"), usage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintSubcommands() {
|
||||
CommandContainer container = container(builder -> builder.addSubcommands(
|
||||
new SubcommandData("add", "Adds").addOptions(new OptionData(OptionType.STRING, "name", "Name", true))));
|
||||
|
||||
String usage = container.printUsage();
|
||||
|
||||
assertTrue(usage.contains("- Usage: /test add <name> (Adds)\n - *name: Name"), usage);
|
||||
assertTrue(usage.endsWith("-# *: required argument"), usage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintSubcommandGroups() {
|
||||
CommandContainer container = container(builder -> builder.addSubcommandGroups(
|
||||
new SubcommandGroupData("grp", "Group").addSubcommands(
|
||||
new SubcommandData("add", "Adds").addOptions(new OptionData(OptionType.STRING, "name", "Name", true)))));
|
||||
|
||||
String usage = container.printUsage();
|
||||
|
||||
assertTrue(usage.contains("- Subcommand: /test grp (Group)\n"), usage);
|
||||
assertTrue(usage.contains(" - Usage: /test grp add <name> (Adds)\n - *name: Name"), usage);
|
||||
assertTrue(usage.endsWith("-# *: required argument"), usage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintExactSubcommandUsage() {
|
||||
CommandContainer container = container(builder -> builder.addSubcommands(
|
||||
new SubcommandData("add", "Adds").addOptions(new OptionData(OptionType.STRING, "name", "Name", true))));
|
||||
|
||||
assertEquals("- Usage: /test add <name> (Adds)\n - *name: Name\n\n\n-# *: required argument",
|
||||
container.printUsage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintExactSubcommandGroupUsage() {
|
||||
CommandContainer container = container(builder -> builder.addSubcommandGroups(
|
||||
new SubcommandGroupData("grp", "Group").addSubcommands(
|
||||
new SubcommandData("add", "Adds").addOptions(new OptionData(OptionType.STRING, "name", "Name", true)))));
|
||||
|
||||
assertEquals("- Subcommand: /test grp (Group)\n"
|
||||
+ " - Usage: /test grp add <name> (Adds)\n"
|
||||
+ " - *name: Name\n\n\n-# *: required argument", container.printUsage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintExactAnnotations() {
|
||||
CommandContainer container = container(builder -> builder.addOptions(
|
||||
new OptionData(OptionType.STRING, "mode", "Mode").setMinLength(1).setMaxLength(5)
|
||||
.addChoice("One", "one").addChoice("Two", "two"),
|
||||
new OptionData(OptionType.INTEGER, "n", "N").setMinValue(1).setMaxValue(9),
|
||||
new OptionData(OptionType.CHANNEL, "ch", "Ch").setChannelTypes(ChannelType.TEXT)));
|
||||
|
||||
assertEquals("- Usage: /test <mode> <n> <ch>\n"
|
||||
+ " - mode: Mode [Min length: 1] [Max length: 5] [Available choices: one, two]\n"
|
||||
+ " - n: N [Min value: 1] [Max value: 9]\n"
|
||||
+ " - ch: Ch [Required channel types: TEXT]\n\n"
|
||||
+ "-# *: required argument", container.printUsage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotPrintTheRequiredLegendForUsagelessCommands() {
|
||||
assertFalse(container(_ -> { }).printUsage().contains("required argument"));
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package me.bobert.netneutrality.core.command;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.core.command.precondition.DisabledCommandPrecondition;
|
||||
import me.bobert.netneutrality.utils.UserFunction;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Dispatches through a real {@link CommandProcessor} with the default preconditions, usage validator and
|
||||
* result handler, so only the Discord entities, the registry and the command itself are mocked.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class CommandDispatchIntegrationTest {
|
||||
private static final String COMMAND_NAME = "test";
|
||||
|
||||
@Mock
|
||||
private CommandContext context;
|
||||
|
||||
@Mock
|
||||
private CommandRegistry commands;
|
||||
|
||||
@Mock
|
||||
private TaskManager taskManager;
|
||||
|
||||
@Mock
|
||||
private Command command;
|
||||
|
||||
@Mock
|
||||
private Guild guild;
|
||||
|
||||
@Mock
|
||||
private Member member;
|
||||
|
||||
@Mock
|
||||
private User user;
|
||||
|
||||
@Mock
|
||||
private MessageChannelUnion channel;
|
||||
|
||||
private CommandProcessor processor;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
UserFunction.bind((GuildServiceProvider) null);
|
||||
DisabledCommandPrecondition.bind(null);
|
||||
|
||||
when(command.getCommandName()).thenReturn(COMMAND_NAME);
|
||||
when(command.getDescription()).thenReturn("A test command");
|
||||
when(command.getType()).thenReturn(Command.Type.CHAT);
|
||||
when(command.isEnabled()).thenReturn(true);
|
||||
when(command.canBeExecutedFrom(channel)).thenReturn(true);
|
||||
when(command.canBeExecutedBy(member)).thenReturn(true);
|
||||
when(command.execute(context)).thenReturn(CommandExecutionResult.success("Executed"));
|
||||
|
||||
CommandContainer container = new CommandContainer(command);
|
||||
when(commands.hasCommand(COMMAND_NAME)).thenReturn(true);
|
||||
when(commands.getCommandContainer(COMMAND_NAME)).thenReturn(container);
|
||||
|
||||
when(context.parseCommandName()).thenReturn(COMMAND_NAME);
|
||||
when(context.getFullCommand()).thenReturn(COMMAND_NAME);
|
||||
when(context.getId()).thenReturn("context-id");
|
||||
when(context.getGuild()).thenReturn(guild);
|
||||
when(context.isFromGuild()).thenReturn(true);
|
||||
when(context.getGuildUser()).thenReturn(member);
|
||||
when(context.getDiscordUser()).thenReturn(user);
|
||||
when(context.getChannel()).thenReturn(channel);
|
||||
when(context.isValid()).thenReturn(true);
|
||||
when(guild.getId()).thenReturn("111");
|
||||
when(member.getId()).thenReturn("222");
|
||||
when(user.getId()).thenReturn("222");
|
||||
when(channel.getId()).thenReturn("333");
|
||||
|
||||
processor = new CommandProcessor(context, commands, taskManager);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
UserFunction.bind((GuildServiceProvider) null);
|
||||
DisabledCommandPrecondition.bind(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockANonAdminOnAnAdminCommand() {
|
||||
when(command.needAdmin()).thenReturn(true);
|
||||
|
||||
processor.dispatch();
|
||||
|
||||
verify(context).replyEphemeral("You are not powerful enough, scrub");
|
||||
verify(command, never()).execute(any());
|
||||
verify(context, never()).reply("Executed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRunAnAllowedCommandToCompletion() {
|
||||
processor.dispatch();
|
||||
|
||||
verify(context).setCommand(command);
|
||||
verify(command).execute(context);
|
||||
verify(context).reply("Executed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockAGuildOnlyCommandInPrivateMessages() {
|
||||
when(command.isGuildOnly()).thenReturn(true);
|
||||
when(context.isFromGuild()).thenReturn(false);
|
||||
|
||||
processor.dispatch();
|
||||
|
||||
verify(context).replyEphemeral("This command is only available in a guild");
|
||||
verify(context).deleteMessage();
|
||||
verify(command, never()).execute(any());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package me.bobert.netneutrality.core.command;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.bootstrap.ApplicationContext;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.interactions.InteractionContextType;
|
||||
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
|
||||
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.RETURNS_SELF;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class CommandManagerTest {
|
||||
|
||||
@Mock
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Mock
|
||||
private JDA jda;
|
||||
|
||||
private CommandListUpdateAction updateAction;
|
||||
|
||||
private CommandManager manager;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
manager = new CommandManager(applicationContext);
|
||||
|
||||
updateAction = mock(CommandListUpdateAction.class, RETURNS_SELF);
|
||||
when(jda.updateCommands()).thenReturn(updateAction);
|
||||
}
|
||||
|
||||
private Command command(String name, String description) {
|
||||
Command command = mock(Command.class);
|
||||
when(command.getCommandName()).thenReturn(name);
|
||||
when(command.getDescription()).thenReturn(description);
|
||||
when(command.getType()).thenReturn(Command.Type.CHAT);
|
||||
when(command.isEnabled()).thenReturn(true);
|
||||
when(command.isSlashCommand()).thenReturn(true);
|
||||
when(command.isGuildOnly()).thenReturn(false);
|
||||
when(command.getSlashCommandPermission()).thenReturn(DefaultMemberPermissions.ENABLED);
|
||||
return command;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<CommandData> registeredCommands() {
|
||||
manager.registerSlashCommands(jda);
|
||||
|
||||
ArgumentCaptor<Collection<CommandData>> captor = ArgumentCaptor.forClass(Collection.class);
|
||||
verify(updateAction).addCommands(captor.capture());
|
||||
verify(updateAction).queue();
|
||||
return List.copyOf(captor.getValue());
|
||||
}
|
||||
|
||||
// ===== Construction =====
|
||||
|
||||
@Test
|
||||
void shouldRequireTheApplicationContext() {
|
||||
assertThrows(NullPointerException.class, () -> new CommandManager(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldStartAfterTheOtherRegistries() {
|
||||
assertEquals(30, manager.priority());
|
||||
}
|
||||
|
||||
// ===== Loading =====
|
||||
|
||||
@Test
|
||||
void shouldRegisterAnEnabledCommand() {
|
||||
Command ping = command("ping", "Pings");
|
||||
|
||||
manager.onCommandLoaded(ping);
|
||||
|
||||
assertTrue(manager.hasCommand("ping"));
|
||||
assertSame(ping, manager.getCommandContainer("ping").getCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipDisabledCommands() {
|
||||
Command ping = command("ping", "Pings");
|
||||
when(ping.isEnabled()).thenReturn(false);
|
||||
|
||||
manager.onCommandLoaded(ping);
|
||||
|
||||
assertFalse(manager.hasCommand("ping"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipCommandsWithoutAName() {
|
||||
manager.onCommandLoaded(command(null, "Pings"));
|
||||
|
||||
assertTrue(manager.getCommands().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipChatCommandsWithoutADescription() {
|
||||
manager.onCommandLoaded(command("ping", null));
|
||||
|
||||
assertFalse(manager.hasCommand("ping"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepMessageCommandsWithoutADescription() {
|
||||
Command menu = command("menu", null);
|
||||
when(menu.getType()).thenReturn(Command.Type.MESSAGE);
|
||||
|
||||
manager.onCommandLoaded(menu);
|
||||
|
||||
assertTrue(manager.hasCommand("menu"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepTheFirstCommandWhenNamesAreDuplicated() {
|
||||
Command first = command("ping", "First");
|
||||
Command second = command("ping", "Second");
|
||||
|
||||
manager.onCommandLoaded(first);
|
||||
manager.onCommandLoaded(second);
|
||||
|
||||
assertEquals(1, manager.getCommands().size());
|
||||
assertSame(first, manager.getCommandContainer("ping").getCommand());
|
||||
}
|
||||
|
||||
// ===== Lookups =====
|
||||
|
||||
@Test
|
||||
void shouldNotHaveAnUnknownCommand() {
|
||||
assertFalse(manager.hasCommand("unknown"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTheNullCommandForAnUnknownName() {
|
||||
assertSame(CommandContainer.NULL_COMMAND, manager.getCommandContainer("unknown"));
|
||||
}
|
||||
|
||||
// ===== Slash command registration =====
|
||||
|
||||
@Test
|
||||
void shouldRegisterEnabledSlashCommands() {
|
||||
manager.onCommandLoaded(command("ping", "Pings"));
|
||||
|
||||
List<CommandData> registered = registeredCommands();
|
||||
|
||||
assertEquals(1, registered.size());
|
||||
assertEquals("ping", registered.getFirst().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterNothingWithoutCommands() {
|
||||
assertTrue(registeredCommands().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRegisterCommandsThatAreNotSlashCommands() {
|
||||
Command ping = command("ping", "Pings");
|
||||
manager.onCommandLoaded(ping);
|
||||
when(ping.isSlashCommand()).thenReturn(false);
|
||||
|
||||
assertTrue(registeredCommands().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRegisterCommandsDisabledAfterLoading() {
|
||||
Command ping = command("ping", "Pings");
|
||||
manager.onCommandLoaded(ping);
|
||||
when(ping.isEnabled()).thenReturn(false);
|
||||
|
||||
assertTrue(registeredCommands().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRestrictGuildOnlyCommandsToGuilds() {
|
||||
Command ping = command("ping", "Pings");
|
||||
when(ping.isGuildOnly()).thenReturn(true);
|
||||
manager.onCommandLoaded(ping);
|
||||
|
||||
SlashCommandData data = (SlashCommandData) registeredCommands().getFirst();
|
||||
|
||||
assertEquals(Set.of(InteractionContextType.GUILD), data.getContexts());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowOtherCommandsInGuildsAndBotDms() {
|
||||
manager.onCommandLoaded(command("ping", "Pings"));
|
||||
|
||||
SlashCommandData data = (SlashCommandData) registeredCommands().getFirst();
|
||||
|
||||
assertEquals(Set.of(InteractionContextType.GUILD, InteractionContextType.BOT_DM), data.getContexts());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplyTheDefaultPermissionOfTheCommand() {
|
||||
Command ping = command("ping", "Pings");
|
||||
when(ping.getSlashCommandPermission()).thenReturn(DefaultMemberPermissions.DISABLED);
|
||||
manager.onCommandLoaded(ping);
|
||||
|
||||
SlashCommandData data = (SlashCommandData) registeredCommands().getFirst();
|
||||
|
||||
assertEquals(DefaultMemberPermissions.DISABLED, data.getDefaultPermissions());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepRegisteringWhenOneCommandFails() {
|
||||
Command broken = command("broken", "Broken");
|
||||
CommandContainer brokenContainer = mock(CommandContainer.class);
|
||||
when(brokenContainer.getCommand()).thenReturn(broken);
|
||||
when(brokenContainer.getSlashCommandData()).thenThrow(new IllegalStateException("boom"));
|
||||
manager.getCommands().put("broken", brokenContainer);
|
||||
manager.onCommandLoaded(command("ping", "Pings"));
|
||||
|
||||
List<CommandData> registered = registeredCommands();
|
||||
|
||||
assertEquals(1, registered.size());
|
||||
assertEquals("ping", registered.getFirst().getName());
|
||||
verify(brokenContainer).getSlashCommandData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package me.bobert.netneutrality.core.command;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class CommandOptionsBuilderTest {
|
||||
|
||||
@Mock
|
||||
private Command command;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(command.getCommandName()).thenReturn("test");
|
||||
when(command.getDescription()).thenReturn("A test command");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBuildSlashDataForChatCommands() {
|
||||
CommandOptionsBuilder builder = new CommandOptionsBuilder(Command.Type.CHAT, command);
|
||||
|
||||
assertNotNull(builder.build());
|
||||
assertEquals(net.dv8tion.jda.api.interactions.commands.Command.Type.SLASH, builder.getType());
|
||||
assertEquals("test", builder.getName());
|
||||
assertEquals("A test command", builder.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBuildUserContextMenuDataForUserCommands() {
|
||||
CommandOptionsBuilder builder = new CommandOptionsBuilder(Command.Type.USER, command);
|
||||
|
||||
assertEquals(net.dv8tion.jda.api.interactions.commands.Command.Type.USER, builder.build().getType());
|
||||
assertEquals("test", builder.build().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBuildMessageContextMenuDataForMessageCommands() {
|
||||
CommandOptionsBuilder builder = new CommandOptionsBuilder(Command.Type.MESSAGE, command);
|
||||
|
||||
assertEquals(net.dv8tion.jda.api.interactions.commands.Command.Type.MESSAGE, builder.build().getType());
|
||||
assertEquals("test", builder.build().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldStartWithoutOptions() {
|
||||
CommandOptionsBuilder builder = new CommandOptionsBuilder(Command.Type.CHAT, command);
|
||||
|
||||
assertTrue(builder.getOptions().isEmpty());
|
||||
assertTrue(builder.getSubcommands().isEmpty());
|
||||
assertTrue(builder.getSubcommandGroups().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDelegateAddedOptions() {
|
||||
CommandOptionsBuilder builder = new CommandOptionsBuilder(Command.Type.CHAT, command);
|
||||
|
||||
builder.addOptions(new OptionData(OptionType.STRING, "text", "Some text", true));
|
||||
|
||||
assertEquals(1, builder.build().getOptions().size());
|
||||
assertEquals("text", builder.getOptions().getFirst().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDelegateAddedSubcommandsAndGroups() {
|
||||
CommandOptionsBuilder builder = new CommandOptionsBuilder(Command.Type.CHAT, command);
|
||||
|
||||
builder.addSubcommands(new SubcommandData("add", "Adds"));
|
||||
builder.addSubcommandGroups(new SubcommandGroupData("grp", "Group").addSubcommands(new SubcommandData("sub", "Sub")));
|
||||
|
||||
assertEquals("add", builder.getSubcommands().getFirst().getName());
|
||||
assertEquals("grp", builder.getSubcommandGroups().getFirst().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDelegateSettersToTheBuiltData() {
|
||||
CommandOptionsBuilder builder = new CommandOptionsBuilder(Command.Type.CHAT, command);
|
||||
|
||||
builder.setNSFW(true);
|
||||
builder.setDescription("Changed");
|
||||
|
||||
assertTrue(builder.build().isNSFW());
|
||||
assertEquals("Changed", builder.build().getDescription());
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ class CommandProcessorTest {
|
||||
when(commands.getCommandContainer(COMMAND_NAME)).thenReturn(container);
|
||||
when(container.getCommand()).thenReturn(command);
|
||||
when(command.getCommandName()).thenReturn(COMMAND_NAME);
|
||||
when(command.getType()).thenReturn(Command.Type.SLASH);
|
||||
when(command.getType()).thenReturn(Command.Type.CHAT);
|
||||
when(command.execute(context)).thenReturn(result);
|
||||
when(usageValidator.validate(context, container)).thenReturn(true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package me.bobert.netneutrality.core.command;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.tasks.Task;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class CommandResultHandlerTest {
|
||||
|
||||
@Mock
|
||||
private CommandContext context;
|
||||
|
||||
@Mock
|
||||
private TaskManager taskManager;
|
||||
|
||||
@Mock
|
||||
private Task task;
|
||||
|
||||
private final CommandResultHandler handler = new CommandResultHandler();
|
||||
|
||||
@Test
|
||||
void shouldDoNothingForNoAction() {
|
||||
handler.handle(context, CommandExecutionResult.none(), taskManager);
|
||||
|
||||
verify(context, never()).reply(anyString());
|
||||
verifyNoInteractions(taskManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInsertTheTaskForATaskResult() {
|
||||
handler.handle(context, CommandExecutionResult.task(task), taskManager);
|
||||
|
||||
verify(taskManager).insert(context, task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotReplyForATaskResultWithoutMessage() {
|
||||
handler.handle(context, CommandExecutionResult.task(task), taskManager);
|
||||
|
||||
verify(context, never()).reply(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReplyForASuccess() {
|
||||
handler.handle(context, CommandExecutionResult.success("Done"), taskManager);
|
||||
|
||||
verify(context).reply("Done");
|
||||
verify(taskManager, never()).insert(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReplyForAnInfo() {
|
||||
handler.handle(context, CommandExecutionResult.info("FYI"), taskManager);
|
||||
|
||||
verify(context).reply("FYI");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReplyForAWarning() {
|
||||
handler.handle(context, CommandExecutionResult.warn("Careful"), taskManager);
|
||||
|
||||
verify(context).reply("Careful");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReplyForAnError() {
|
||||
handler.handle(context, CommandExecutionResult.error("Broken", new IllegalStateException("boom")), taskManager);
|
||||
|
||||
verify(context).reply("Broken");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReplyForAnErrorWithoutException() {
|
||||
handler.handle(context, CommandExecutionResult.error("Broken"), taskManager);
|
||||
|
||||
verify(context).reply("Broken");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotReplyWhenTheMessageIsNull() {
|
||||
handler.handle(context, CommandExecutionResult.success((String) null), taskManager);
|
||||
|
||||
verify(context, never()).reply(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailForANullResult() {
|
||||
when(context.getId()).thenReturn("context-id");
|
||||
|
||||
assertThrows(RuntimeException.class, () -> handler.handle(context, null, taskManager));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package me.bobert.netneutrality.core.command;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class CommandUsageFormatterTest {
|
||||
|
||||
private final CommandUsageFormatter formatter = new CommandUsageFormatter();
|
||||
|
||||
private Command command() {
|
||||
Command command = mock(Command.class);
|
||||
when(command.getCommandName()).thenReturn("test");
|
||||
when(command.getDescription()).thenReturn("A test command");
|
||||
when(command.getType()).thenReturn(Command.Type.CHAT);
|
||||
return command;
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFormatEmptyContainerAsEmptyString() {
|
||||
assertEquals("", formatter.format(new CommandContainer(command())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFormatOptionsWithRequiredMarkerAndLegend() {
|
||||
Command command = command();
|
||||
doAnswer(invocation -> {
|
||||
CommandOptionsBuilder builder = invocation.getArgument(0);
|
||||
builder.addOptions(new OptionData(OptionType.STRING, "text", "Text", true));
|
||||
return null;
|
||||
}).when(command).registerCommandOptions(org.mockito.ArgumentMatchers.any());
|
||||
|
||||
assertEquals("- Usage: /test <text>\n - *text: Text\n\n-# *: required argument",
|
||||
formatter.format(new CommandContainer(command)));
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package me.bobert.netneutrality.core.command.precondition;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.command.Command;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.db.GSettings;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.db.service.GuildSettingService;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class DisabledCommandPreconditionTest {
|
||||
|
||||
@Mock
|
||||
private CommandContext context;
|
||||
|
||||
@Mock
|
||||
private Command command;
|
||||
|
||||
@Mock
|
||||
private Guild guild;
|
||||
|
||||
@Mock
|
||||
private GuildServiceProvider provider;
|
||||
|
||||
@Mock
|
||||
private GuildService guildService;
|
||||
|
||||
@Mock
|
||||
private GuildSettingService settingService;
|
||||
|
||||
private final DisabledCommandPrecondition precondition = new DisabledCommandPrecondition();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(context.getGuild()).thenReturn(guild);
|
||||
when(guild.getId()).thenReturn("111");
|
||||
when(command.getCommandName()).thenReturn("ping");
|
||||
when(provider.get("111")).thenReturn(guildService);
|
||||
when(guildService.getGuildSettingService()).thenReturn(settingService);
|
||||
when(settingService.getOrDefault(GSettings.DISABLED_COMMANDS)).thenReturn("");
|
||||
|
||||
DisabledCommandPrecondition.bind(provider);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
DisabledCommandPrecondition.bind(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassWhenNoProviderIsBound() {
|
||||
DisabledCommandPrecondition.bind(null);
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassOutsideAGuild() {
|
||||
when(context.getGuild()).thenReturn(null);
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassWhenNoCommandIsDisabled() {
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassWhenAnotherCommandIsDisabled() {
|
||||
when(settingService.getOrDefault(GSettings.DISABLED_COMMANDS)).thenReturn("play,skip");
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockADisabledCommand() {
|
||||
when(settingService.getOrDefault(GSettings.DISABLED_COMMANDS)).thenReturn("play,ping");
|
||||
|
||||
assertFalse(precondition.check(context, command));
|
||||
|
||||
verify(context).replyEphemeral("This command is disabled on this server");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreCaseAndWhitespaceWhenMatching() {
|
||||
when(settingService.getOrDefault(GSettings.DISABLED_COMMANDS)).thenReturn("play, PING ");
|
||||
|
||||
assertFalse(precondition.check(context, command));
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package me.bobert.netneutrality.core.command.precondition;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.command.Command;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class GuildOnlyPreconditionTest {
|
||||
|
||||
@Mock
|
||||
private CommandContext context;
|
||||
|
||||
@Mock
|
||||
private Command command;
|
||||
|
||||
private final GuildOnlyPrecondition precondition = new GuildOnlyPrecondition();
|
||||
|
||||
@Test
|
||||
void shouldPassGuildOnlyCommandInAGuild() {
|
||||
when(command.isGuildOnly()).thenReturn(true);
|
||||
when(context.isFromGuild()).thenReturn(true);
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(org.mockito.ArgumentMatchers.anyString());
|
||||
verify(context, never()).deleteMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockGuildOnlyCommandOutsideAGuild() {
|
||||
when(command.isGuildOnly()).thenReturn(true);
|
||||
when(context.isFromGuild()).thenReturn(false);
|
||||
|
||||
assertFalse(precondition.check(context, command));
|
||||
|
||||
verify(context).replyEphemeral("This command is only available in a guild");
|
||||
verify(context).deleteMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassCommandThatIsNotGuildOnlyOutsideAGuild() {
|
||||
when(command.isGuildOnly()).thenReturn(false);
|
||||
when(context.isFromGuild()).thenReturn(false);
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(org.mockito.ArgumentMatchers.anyString());
|
||||
verify(context, never()).deleteMessage();
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package me.bobert.netneutrality.core.command.precondition;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.command.Command;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.utils.UserFunction;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class PermissionPreconditionTest {
|
||||
private static final String DENIED = "You are not powerful enough, scrub";
|
||||
|
||||
@Mock
|
||||
private CommandContext context;
|
||||
|
||||
@Mock
|
||||
private Command command;
|
||||
|
||||
@Mock
|
||||
private Member member;
|
||||
|
||||
@Mock
|
||||
private Role role;
|
||||
|
||||
@Mock
|
||||
private MessageChannelUnion channel;
|
||||
|
||||
private final PermissionPrecondition precondition = new PermissionPrecondition();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
UserFunction.bind(null);
|
||||
|
||||
when(command.getType()).thenReturn(Command.Type.CHAT);
|
||||
when(context.canSkipPermissionCheck()).thenReturn(false);
|
||||
when(context.getChannel()).thenReturn(channel);
|
||||
when(context.getGuildUser()).thenReturn(member);
|
||||
when(command.canBeExecutedFrom(channel)).thenReturn(true);
|
||||
when(command.needAdmin()).thenReturn(false);
|
||||
when(command.canBeExecutedBy(member)).thenReturn(true);
|
||||
when(member.getRoles()).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
UserFunction.bind(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipTheCheckForMessageCommands() {
|
||||
when(command.getType()).thenReturn(Command.Type.MESSAGE);
|
||||
when(command.canBeExecutedFrom(channel)).thenReturn(false);
|
||||
when(command.canBeExecutedBy(member)).thenReturn(false);
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassWhenThePermissionCheckCanBeSkipped() {
|
||||
when(context.canSkipPermissionCheck()).thenReturn(true);
|
||||
when(command.canBeExecutedFrom(channel)).thenReturn(false);
|
||||
when(command.canBeExecutedBy(member)).thenReturn(false);
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockWhenTheCommandCannotRunInTheChannel() {
|
||||
when(command.canBeExecutedFrom(channel)).thenReturn(false);
|
||||
|
||||
assertFalse(precondition.check(context, command));
|
||||
|
||||
verify(context).replyEphemeral("This command cannot be executed in the current channel");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockAnAdminCommandForANonAdmin() {
|
||||
when(command.needAdmin()).thenReturn(true);
|
||||
|
||||
assertFalse(precondition.check(context, command));
|
||||
|
||||
verify(context).replyEphemeral(DENIED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassAnAdminCommandForAnAdmin() {
|
||||
when(command.needAdmin()).thenReturn(true);
|
||||
when(role.getPermissions()).thenReturn(EnumSet.of(Permission.ADMINISTRATOR));
|
||||
when(member.getRoles()).thenReturn(List.of(role));
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockWhenTheMemberCannotExecuteTheCommand() {
|
||||
when(command.canBeExecutedBy(member)).thenReturn(false);
|
||||
|
||||
assertFalse(precondition.check(context, command));
|
||||
|
||||
verify(context).replyEphemeral(DENIED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassWhenEveryCheckSucceeds() {
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(anyString());
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package me.bobert.netneutrality.core.command.precondition;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.command.Command;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class SlashCommandOnlyPreconditionTest {
|
||||
|
||||
@Mock
|
||||
private CommandContext context;
|
||||
|
||||
@Mock
|
||||
private Command command;
|
||||
|
||||
private final SlashCommandOnlyPrecondition precondition = new SlashCommandOnlyPrecondition();
|
||||
|
||||
@Test
|
||||
void shouldPassSlashOnlyCommandFromAnInteraction() {
|
||||
when(command.isSlashCommandOnly()).thenReturn(true);
|
||||
when(context.isInteraction()).thenReturn(true);
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBlockSlashOnlyCommandSentAsAMessage() {
|
||||
when(command.isSlashCommandOnly()).thenReturn(true);
|
||||
when(context.isInteraction()).thenReturn(false);
|
||||
|
||||
assertFalse(precondition.check(context, command));
|
||||
|
||||
verify(context).replyEphemeral("This command is only available as a slash command");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassCommandThatIsNotSlashOnlyAsAMessage() {
|
||||
when(command.isSlashCommandOnly()).thenReturn(false);
|
||||
when(context.isInteraction()).thenReturn(false);
|
||||
|
||||
assertTrue(precondition.check(context, command));
|
||||
|
||||
verify(context, never()).replyEphemeral(anyString());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user