diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandManagerTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandManagerTest.java new file mode 100644 index 00000000..258db4dd --- /dev/null +++ b/cloud-core/src/test/java/cloud/commandframework/CommandManagerTest.java @@ -0,0 +1,178 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework; + +import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.standard.IntegerArgument; +import cloud.commandframework.context.CommandContext; +import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.execution.CommandExecutionHandler; +import cloud.commandframework.internal.CommandRegistrationHandler; +import cloud.commandframework.meta.CommandMeta; +import cloud.commandframework.meta.SimpleCommandMeta; +import io.leangen.geantyref.TypeToken; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link CommandManager} integration tests. + */ +@SuppressWarnings("unchecked") +class CommandManagerTest { + + private CommandManager commandManager; + + @BeforeEach + void setup() { + this.commandManager = new CommandManager( + CommandExecutionCoordinator.simpleCoordinator(), + CommandRegistrationHandler.nullCommandRegistrationHandler() + ) { + @Override + public boolean hasPermission( + final @NonNull TestCommandSender sender, + final @NonNull String permission + ) { + return true; + } + + @Override + public @NonNull CommandMeta createDefaultCommandMeta() { + return SimpleCommandMeta.empty(); + } + }; + } + + @Test + void testMultiLiteralRouting() { + // Arrange + final CommandExecutionHandler handlerA = mock(CommandExecutionHandler.class); + when(handlerA.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null)); + + final CommandExecutionHandler handlerB = mock(CommandExecutionHandler.class); + when(handlerB.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null)); + + final CommandExecutionHandler handlerC = mock(CommandExecutionHandler.class); + when(handlerC.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null)); + + final Command commandA = this.commandManager.commandBuilder("test") + .literal("a") + .handler(handlerA) + .build(); + final Command commandB = this.commandManager.commandBuilder("test") + .literal("b") + .handler(handlerB) + .build(); + final Command commandC = this.commandManager.commandBuilder("test") + .literal("c") + .argument(IntegerArgument.optional("opt")) + .handler(handlerC) + .build(); + + this.commandManager.command(commandA); + this.commandManager.command(commandB); + this.commandManager.command(commandC); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "test a").join(); + this.commandManager.executeCommand(new TestCommandSender(), "test b").join(); + this.commandManager.executeCommand(new TestCommandSender(), "test c").join(); + this.commandManager.executeCommand(new TestCommandSender(), "test c 123").join(); + + // Assert + ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + + verify(handlerA).executeFuture(contextArgumentCaptor.capture()); + final CommandContext contextA = contextArgumentCaptor.getValue(); + assertThat(contextA.getRawInputJoined()).isEqualTo("test a"); + + verify(handlerB).executeFuture(contextArgumentCaptor.capture()); + final CommandContext contextB = contextArgumentCaptor.getValue(); + assertThat(contextB.getRawInputJoined()).isEqualTo("test b"); + + // Reset captor to reset the list of values. + contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(handlerC, times(2)).executeFuture(contextArgumentCaptor.capture()); + final CommandContext contextC1 = contextArgumentCaptor.getAllValues().get(0); + assertThat(contextC1.getRawInputJoined()).isEqualTo("test c"); + final CommandContext contextC2 = contextArgumentCaptor.getAllValues().get(1); + assertThat(contextC2.getRawInputJoined()).isEqualTo("test c 123"); + } + + @Test + void testCommandBuilder() { + // Create and register a command + Command command = this.commandManager.commandBuilder("component") + .literal("literal", "literalalias") + .literal("detail", ArgumentDescription.of("detaildescription")) + .argument( + CommandArgument.ofType(int.class, "argument"), + ArgumentDescription.of("argumentdescription") + ) + .build(); + this.commandManager.command(command); + + // Verify all the details we have configured are present + List> arguments = command.getArguments(); + List> components = command.getComponents(); + assertThat(arguments.size()).isEqualTo(components.size()); + assertThat(components.size()).isEqualTo(4);; + + // Arguments should exactly match the component getArgument() result, in same order + for (int i = 0; i < components.size(); i++) { + assertThat(components.get(i).getArgument()).isEqualTo(arguments.get(i)); + } + + // Argument configuration, we know component has the same argument so no need to test those + // TODO: Aliases + assertThat(arguments.get(0).getName()).isEqualTo("component");; + assertThat(arguments.get(1).getName()).isEqualTo("literal");; + assertThat(arguments.get(2).getName()).isEqualTo("detail");; + assertThat(arguments.get(3).getName()).isEqualTo("argument");; + + // Check argument is indeed a command argument + assertThat(TypeToken.get(int.class)).isEqualTo(arguments.get(3).getValueType()); + + // Check description is set for all components, is empty when not specified + assertThat(components.get(0).getArgumentDescription().getDescription()).isEmpty(); + assertThat(components.get(1).getArgumentDescription().getDescription()).isEmpty(); + assertThat(components.get(2).getArgumentDescription().getDescription()).isEqualTo("detaildescription");; + assertThat(components.get(3).getArgumentDescription().getDescription()).isEqualTo("argumentdescription");; + } +} diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandPostProcessorTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandPostProcessorTest.java index fa5e7b0e..d5ea7049 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandPostProcessorTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandPostProcessorTest.java @@ -23,44 +23,62 @@ // package cloud.commandframework; +import cloud.commandframework.execution.CommandExecutionHandler; import cloud.commandframework.execution.postprocessor.CommandPostprocessingContext; import cloud.commandframework.execution.postprocessor.CommandPostprocessor; -import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.services.types.ConsumerService; +import java.util.concurrent.CompletableFuture; import org.checkerframework.checker.nullness.qual.NonNull; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static cloud.commandframework.util.TestUtils.createManager; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -public class CommandPostProcessorTest { +@SuppressWarnings("unchecked") +class CommandPostProcessorTest { - private static final boolean[] state = new boolean[]{false}; - private static CommandManager manager; + private CommandManager commandManager; - @BeforeAll - static void newTree() { - manager = createManager(); - manager.command(manager.commandBuilder("test", SimpleCommandMeta.empty()) - .handler(c -> state[0] = true) - .build()); - manager.registerCommandPostProcessor(new SamplePostprocessor()); + @BeforeEach + void setup() { + this.commandManager = createManager(); } @Test - void testPreprocessing() { - manager.executeCommand(new TestCommandSender(), "test").join(); - Assertions.assertFalse(state[0]); + void testPostProcessing() { + // Arrange + final CommandPostprocessor postprocessor = spy(new SamplePostprocessor()); + this.commandManager.registerCommandPostProcessor(postprocessor); + + final CommandExecutionHandler executionHandler = mock(CommandExecutionHandler.class); + when(executionHandler.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null)); + + this.commandManager.command( + this.commandManager.commandBuilder("test") + .handler(executionHandler) + .build() + ); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "test").join(); + + // Assert + verify(executionHandler, never()).executeFuture(notNull()); + verify(postprocessor).accept(notNull()); } - static final class SamplePostprocessor implements CommandPostprocessor { + static class SamplePostprocessor implements CommandPostprocessor { @Override public void accept(final @NonNull CommandPostprocessingContext context) { ConsumerService.interrupt(); } - } - } diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java index 15bb7363..9ff3f20e 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java @@ -24,9 +24,7 @@ package cloud.commandframework; import cloud.commandframework.arguments.CommandArgument; -import cloud.commandframework.arguments.compound.ArgumentPair; import cloud.commandframework.arguments.flags.CommandFlag; -import cloud.commandframework.arguments.preprocessor.RegexPreprocessor; import cloud.commandframework.arguments.standard.EnumArgument; import cloud.commandframework.arguments.standard.FloatArgument; import cloud.commandframework.arguments.standard.IntegerArgument; @@ -34,6 +32,8 @@ import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.context.CommandContext; import cloud.commandframework.exceptions.AmbiguousNodeException; import cloud.commandframework.exceptions.NoPermissionException; +import cloud.commandframework.execution.CommandExecutionHandler; +import cloud.commandframework.keys.SimpleCloudKey; import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.types.tuples.Pair; import io.leangen.geantyref.TypeToken; @@ -41,344 +41,412 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import static cloud.commandframework.util.TestUtils.createManager; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +/** + * {@link CommandTree} integration tests. + */ +@SuppressWarnings("unchecked") class CommandTreeTest { - private static final int EXPECTED_INPUT_NUMBER = 15; - private static CommandManager manager; + private CommandManager commandManager; - @BeforeAll - static void newTree() { - manager = createManager(); + @BeforeEach + void setup() { + this.commandManager = createManager(); + } - /* Build general test commands */ - manager.command(manager.commandBuilder("test", SimpleCommandMeta.empty()) - .literal("one").build()) - .command(manager.commandBuilder("test", SimpleCommandMeta.empty()) - .literal("two").permission("no").build()) - .command(manager.commandBuilder("test", Collections.singleton("other"), - SimpleCommandMeta.empty() - ) - .literal("opt", "öpt") - .argument(IntegerArgument - .optional("num", EXPECTED_INPUT_NUMBER)) - .build()) - .command(manager.commandBuilder("req").senderType(SpecificCommandSender.class).build()); - - /* Build command to test command proxying */ - final Command toProxy = manager.commandBuilder("test") - .literal("unproxied") - .argument(StringArgument.of("string")) - .argument(IntegerArgument.of("int")) - .literal("anotherliteral") - .handler(c -> { - }) - .build(); - manager.command(toProxy); - manager.command(manager.commandBuilder("proxy").proxies(toProxy).build()); - - /* Build command for testing intermediary and final executors */ - manager.command(manager.commandBuilder("command") - .permission("command.inner") - .literal("inner") - .handler(c -> System.out.println("Using inner command"))); - manager.command(manager.commandBuilder("command") - .permission("command.outer") - .handler(c -> System.out.println("Using outer command"))); - - /* Build command for testing compound types */ - manager.command(manager.commandBuilder("pos") - .argument(ArgumentPair.of(manager, "pos", Pair.of("x", "y"), - Pair.of(Integer.class, Integer.class) - ) - .simple()) - .handler(c -> { - final Pair pair = c.get("pos"); - System.out.printf("X: %d | Y: %d\n", pair.getFirst(), pair.getSecond()); - })); - manager.command(manager.commandBuilder("vec") - .argument(ArgumentPair.of(manager, "vec", Pair.of("x", "y"), - Pair.of(Double.class, Double.class) - ) - .withMapper( - Vector2.class, - (sender, pair) -> new Vector2(pair.getFirst(), pair.getSecond()) - ) - ) - .handler(c -> { - final Vector2 vector2 = c.get("vec"); - System.out.printf("X: %f | Y: %f\n", vector2.getX(), vector2.getY()); - })); - - /* Build command for testing flags */ - final CommandFlag test = manager.flagBuilder("test") - .withAliases("t") - .build(); - - final CommandFlag num = manager.flagBuilder("num") - .withArgument(IntegerArgument.of("num")) - .build(); - - manager.command(manager.commandBuilder("flags") - .flag(manager.flagBuilder("test") - .withAliases("t") - .build()) - .flag(manager.flagBuilder("test2") - .withAliases("f") - .build()) - .flag(num) - .flag(manager.flagBuilder("enum") - .withArgument(EnumArgument.of(FlagEnum.class, "enum"))) - .handler(c -> { - System.out.println("Flag present? " + c.flags().isPresent(test)); - System.out.println("Second flag present? " + c.flags().isPresent("test2")); - System.out.println("Numerical flag: " + c.flags().getValue(num, -10)); - System.out.println("Enum: " + c.flags().getValue("enum", FlagEnum.PROXI)); - }) - .build()); - - /* Build command for testing float */ - manager.command(manager.commandBuilder("float") - .argument(FloatArgument.of("num")) - .handler(c -> System.out.printf("%f\n", c.get("num")))); - - /* Build command for testing preprocessing */ - manager.command(manager.commandBuilder("preprocess") - .argument( - StringArgument.of("argument") - .addPreprocessor(RegexPreprocessor.of("[A-Za-z]{3,5}")) - ) + @Test + void testMultiLiteralParsing() { + // Arrange + final int defaultInputNumber = ThreadLocalRandom.current().nextInt(); + this.commandManager.command( + this.commandManager.commandBuilder("test", SimpleCommandMeta.empty()) + .literal("one") + .build() + ).command( + this.commandManager.commandBuilder("test", SimpleCommandMeta.empty()) + .literal("two") + .permission("no") + .build() + ).command( + this.commandManager.commandBuilder("test", SimpleCommandMeta.empty()) + .literal("opt") + .argument(IntegerArgument.optional("num", defaultInputNumber)) + .build() ); - /* Build command for testing multiple optionals */ - manager.command( - manager.commandBuilder("optionals") - .argument(StringArgument.optional("opt1")) - .argument(StringArgument.optional("opt2")) + // Act + final Pair, Exception> command1 = this.commandManager.getCommandTree().parse( + new CommandContext<>(new TestCommandSender(), this.commandManager), + new LinkedList<>(Arrays.asList("test", "one")) ); + final Pair, Exception> command2 = this.commandManager.getCommandTree().parse( + new CommandContext<>(new TestCommandSender(), this.commandManager), + new LinkedList<>(Arrays.asList("test", "two")) + ); + final Pair, Exception> command3 = this.commandManager.getCommandTree().parse( + new CommandContext<>(new TestCommandSender(), this.commandManager), + new LinkedList<>(Arrays.asList("test", "opt")) + ); + final Pair, Exception> command4 = this.commandManager.getCommandTree().parse( + new CommandContext<>(new TestCommandSender(), this.commandManager), + new LinkedList<>(Arrays.asList("test", "opt", "12")) + ); + + // Assert + assertThat(command1.getFirst()).isNotNull(); + assertThat(command1.getSecond()).isNull(); + + assertThat(command2.getFirst()).isNull(); + assertThat(command2.getSecond()).isInstanceOf(NoPermissionException.class); + + assertThat(command3.getFirst()).isNotNull(); + assertThat(command3.getFirst().toString()).isEqualTo("test opt num"); + assertThat(command3.getSecond()).isNull(); + + assertThat(command4.getFirst()).isNotNull(); + assertThat(command4.getFirst().toString()).isEqualTo("test opt num"); + assertThat(command4.getSecond()).isNull(); } @Test - void parse() { - final Pair, Exception> command = manager.getCommandTree() - .parse( - new CommandContext<>( - new TestCommandSender(), - manager - ), - new LinkedList<>( - Arrays.asList( - "test", - "one" - )) - ); - Assertions.assertNotNull(command.getFirst()); - Assertions.assertEquals(NoPermissionException.class, manager.getCommandTree() - .parse( - new CommandContext<>( - new TestCommandSender(), - manager - ), - new LinkedList<>( - Arrays.asList("test", "two")) - ) - .getSecond().getClass()); - manager.getCommandTree() - .parse( - new CommandContext<>(new TestCommandSender(), manager), - new LinkedList<>(Arrays.asList("test", "opt")) - ) - .getFirst().getCommandExecutionHandler().execute(new CommandContext<>( - new TestCommandSender(), - manager - )); - manager.getCommandTree() - .parse( - new CommandContext<>(new TestCommandSender(), manager), - new LinkedList<>(Arrays.asList("test", "opt", "12")) - ) - .getFirst().getCommandExecutionHandler().execute(new CommandContext<>( - new TestCommandSender(), - manager - )); - } - - @Test - void testAlias() { - manager.getCommandTree() - .parse( - new CommandContext<>( - new TestCommandSender(), - manager - ), - new LinkedList<>(Arrays.asList( - "other", - "öpt", - "12" - )) - ) - .getFirst().getCommandExecutionHandler().execute(new CommandContext<>( - new TestCommandSender(), - manager - )); - } - - @Test - void getArgumentsAndComponents() { - // Create and register a command - Command command = manager.commandBuilder("component") - .literal("literal", "literalalias") - .literal("detail", ArgumentDescription.of("detaildescription")) - .argument(CommandArgument.ofType(int.class, "argument"), - ArgumentDescription.of("argumentdescription")) + void testAliasedRouting() { + // Arrange + final int defaultInputNumber = ThreadLocalRandom.current().nextInt(); + final Command command = this.commandManager.commandBuilder( + "test", Collections.singleton("other"), SimpleCommandMeta.empty() + ).literal("opt", "öpt") + .argument(IntegerArgument.optional("num", defaultInputNumber)) .build(); - manager.command(command); + this.commandManager.command(command); - // Verify all the details we have configured are present - List> arguments = command.getArguments(); - List> components = command.getComponents(); - Assertions.assertEquals(arguments.size(), components.size()); - Assertions.assertEquals(4, components.size()); + // Act + final Command result = this.commandManager.getCommandTree().parse( + new CommandContext<>(new TestCommandSender(), this.commandManager), + new LinkedList<>(Arrays.asList("other", "öpt", "12")) + ).getFirst(); - // Arguments should exactly match the component getArgument() result, in same order - for (int i = 0; i < components.size(); i++) { - Assertions.assertEquals(components.get(i).getArgument(), arguments.get(i)); - } - - // Argument configuration, we know component has the same argument so no need to test those - // TODO: Aliases - Assertions.assertEquals("component", arguments.get(0).getName()); - Assertions.assertEquals("literal", arguments.get(1).getName()); - Assertions.assertEquals("detail", arguments.get(2).getName()); - Assertions.assertEquals("argument", arguments.get(3).getName()); - - // Check argument is indeed a command argument - Assertions.assertEquals(TypeToken.get(int.class), arguments.get(3).getValueType()); - - // Check description is set for all components, is empty when not specified - Assertions.assertEquals("", components.get(0).getArgumentDescription().getDescription()); - Assertions.assertEquals("", components.get(1).getArgumentDescription().getDescription()); - Assertions.assertEquals("detaildescription", components.get(2).getArgumentDescription().getDescription()); - Assertions.assertEquals("argumentdescription", components.get(3).getArgumentDescription().getDescription()); + // Assert + assertThat(result).isEqualTo(command); } @Test void getSuggestions() { - Assertions.assertFalse( - manager.getCommandTree().getSuggestions( - new CommandContext<>(new TestCommandSender(), manager), - new LinkedList<>(Arrays.asList("test", "")) - ).isEmpty()); - } + // Arrange + this.commandManager.command( + this.commandManager.commandBuilder("test") + .literal("a") + ); + this.commandManager.command( + this.commandManager.commandBuilder("test") + .literal("b") + ); - @Test - void testRequiredSender() { - Assertions.assertThrows(CompletionException.class, () -> - manager.executeCommand(new TestCommandSender(), "req").join()); + // Act + final List results = this.commandManager.getCommandTree().getSuggestions( + new CommandContext<>(new TestCommandSender(), this.commandManager), + new LinkedList<>(Arrays.asList("test", "")) + ); + + // Assert + assertThat(results).containsExactly("a", "b"); } @Test void testDefaultParser() { - manager.command( - manager.commandBuilder("default") - .argument(manager.argumentBuilder(Integer.class, "int")) - .handler(context -> { - final int number = context.get("int"); - System.out.printf("Supplied number is: %d\n", number); - }) + // Arrange + final CommandExecutionHandler executionHandler = mock(CommandExecutionHandler.class); + when(executionHandler.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null)); + + this.commandManager.command( + this.commandManager.commandBuilder("default") + .argument(this.commandManager.argumentBuilder(Integer.class, "int")) + .handler(executionHandler) + .build() ); - manager.executeCommand(new TestCommandSender(), "default 5").join(); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "default 5").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler).executeFuture(contextArgumentCaptor.capture()); + + final CommandContext context = contextArgumentCaptor.getValue(); + assertThat(context.get(SimpleCloudKey.of("int", TypeToken.get(Integer.class)))).isEqualTo(5); } @Test void invalidCommand() { - Assertions.assertThrows(CompletionException.class, () -> manager + assertThrows(CompletionException.class, () -> this.commandManager .executeCommand(new TestCommandSender(), "invalid test").join()); } @Test void testProxy() { - manager.executeCommand(new TestCommandSender(), "test unproxied foo 10 anotherliteral").join(); - manager.executeCommand(new TestCommandSender(), "proxy foo 10").join(); + // Arrange + final CommandExecutionHandler executionHandler = mock(CommandExecutionHandler.class); + when(executionHandler.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null)); + + final Command toProxy = this.commandManager.commandBuilder("test") + .literal("unproxied") + .argument(StringArgument.of("string")) + .argument(IntegerArgument.of("int")) + .literal("anotherliteral") + .handler(executionHandler) + .build(); + this.commandManager.command(toProxy); + this.commandManager.command(this.commandManager.commandBuilder("proxy").proxies(toProxy).build()); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "test unproxied foo 10 anotherliteral").join(); + this.commandManager.executeCommand(new TestCommandSender(), "proxy foo 10").join(); + + // Assert + verify(executionHandler, times(2)).executeFuture(notNull()); + } + + private CommandExecutionHandler setupFlags() { + final CommandExecutionHandler executionHandler = mock(CommandExecutionHandler.class); + when(executionHandler.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null)); + + final CommandFlag num = this.commandManager.flagBuilder("num") + .withArgument(IntegerArgument.of("num")) + .build(); + + this.commandManager.command(this.commandManager.commandBuilder("flags") + .flag(this.commandManager.flagBuilder("test") + .withAliases("t") + .build()) + .flag(this.commandManager.flagBuilder("test2") + .withAliases("f") + .build()) + .flag(num) + .flag(this.commandManager.flagBuilder("enum") + .withArgument(EnumArgument.of(FlagEnum.class, "enum"))) + .handler(executionHandler) + .build()); + + return executionHandler; } @Test - void testIntermediary() { - manager.executeCommand(new TestCommandSender(), "command inner").join(); - manager.executeCommand(new TestCommandSender(), "command").join(); + void testFlags_NoFlags() { + // Arrange + final CommandExecutionHandler executionHandler = this.setupFlags(); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "flags").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler).executeFuture(contextArgumentCaptor.capture()); + + final CommandContext context = contextArgumentCaptor.getValue(); + assertThat(context.flags().contains("test")).isFalse(); } @Test - void testCompound() { - manager.executeCommand(new TestCommandSender(), "pos -3 2").join(); - manager.executeCommand(new TestCommandSender(), "vec 1 1").join(); + void testFlags_PresenceFlag() { + // Arrange + final CommandExecutionHandler executionHandler = this.setupFlags(); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "flags --test").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler).executeFuture(contextArgumentCaptor.capture()); + + final CommandContext context = contextArgumentCaptor.getValue(); + assertThat(context.flags().contains("test")).isTrue(); } @Test - void testFlags() { - manager.executeCommand(new TestCommandSender(), "flags").join(); - manager.executeCommand(new TestCommandSender(), "flags --test").join(); - manager.executeCommand(new TestCommandSender(), "flags -t").join(); - Assertions.assertThrows(CompletionException.class, () -> - manager.executeCommand(new TestCommandSender(), "flags --test --nonexistant").join()); - Assertions.assertThrows(CompletionException.class, () -> - manager.executeCommand(new TestCommandSender(), "flags --test --duplicate").join()); - manager.executeCommand(new TestCommandSender(), "flags --test --test2").join(); - Assertions.assertThrows(CompletionException.class, () -> - manager.executeCommand(new TestCommandSender(), "flags --test test2").join()); - manager.executeCommand(new TestCommandSender(), "flags --num 500").join(); - manager.executeCommand(new TestCommandSender(), "flags --num 63 --enum potato --test").join(); - manager.executeCommand(new TestCommandSender(), "flags -tf --num 63 --enum potato").join(); + void testFlags_PresenceFlagShortForm() { + // Arrange + final CommandExecutionHandler executionHandler = this.setupFlags(); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "flags -t").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler).executeFuture(contextArgumentCaptor.capture()); + + final CommandContext context = contextArgumentCaptor.getValue(); + assertThat(context.flags().contains("test")).isTrue(); + } + + @Test + void testFlags_NonexistentFlag() { + // Arrange + this.setupFlags(); + + // Act & Assert + assertThrows( + CompletionException.class, () -> + this.commandManager.executeCommand(new TestCommandSender(), "flags --test --nonexistent").join() + ); + } + + @Test + void testFlags_MultiplePresenceFlags() { + // Arrange + final CommandExecutionHandler executionHandler = this.setupFlags(); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "flags --test --test2").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler).executeFuture(contextArgumentCaptor.capture()); + + final CommandContext context = contextArgumentCaptor.getValue(); + assertThat(context.flags().contains("test")).isTrue(); + assertThat(context.flags().contains("test2")).isTrue(); + } + + @Test + void testFlags_NonPrefixedPresenceFlag() { + // Arrange + this.setupFlags(); + + // Act + assertThrows( + CompletionException.class, () -> + this.commandManager.executeCommand(new TestCommandSender(), "flags --test test2").join() + ); + } + + @Test + void testFlags_ValueFlag() { + // Arrange + final CommandExecutionHandler executionHandler = this.setupFlags(); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "flags --num 500").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler).executeFuture(contextArgumentCaptor.capture()); + + final CommandContext context = contextArgumentCaptor.getValue(); + assertThat(context.flags().getValue("num")).hasValue(500); + } + + @Test + void testFlags_MultipleValueFlagsFollowedByPresence() { + // Arrange + final CommandExecutionHandler executionHandler = this.setupFlags(); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "flags --num 63 --enum potato --test").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler).executeFuture(contextArgumentCaptor.capture()); + + final CommandContext context = contextArgumentCaptor.getValue(); + assertThat(context.flags().getValue("num")).hasValue(63); + assertThat(context.flags().getValue("enum")).hasValue(FlagEnum.POTATO); + } + + @Test + void testFlags_ShortFormPresenceFlagsFollowedByMultipleValueFlags() { + // Arrange + final CommandExecutionHandler executionHandler = this.setupFlags(); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "flags -tf --num 63 --enum potato").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler).executeFuture(contextArgumentCaptor.capture()); + + final CommandContext context = contextArgumentCaptor.getValue(); + assertThat(context.flags().contains("test")).isTrue(); + assertThat(context.flags().contains("test2")).isTrue(); + assertThat(context.flags().getValue("num")).hasValue(63); + assertThat(context.flags().getValue("enum")).hasValue(FlagEnum.POTATO); } @Test void testAmbiguousNodes() { - // Call newTree(); after each time we leave the Tree in an invalid state - manager.command(manager.commandBuilder("ambiguous") + // Call setup(); after each time we leave the Tree in an invalid state + this.commandManager.command(this.commandManager.commandBuilder("ambiguous") .argument(StringArgument.of("string")) ); - Assertions.assertThrows(AmbiguousNodeException.class, () -> - manager.command(manager.commandBuilder("ambiguous") + assertThrows(AmbiguousNodeException.class, () -> + this.commandManager.command(this.commandManager.commandBuilder("ambiguous") .argument(IntegerArgument.of("integer")))); - newTree(); + this.setup(); // Literal and argument can co-exist, not ambiguous - manager.command(manager.commandBuilder("ambiguous") + this.commandManager.command(this.commandManager.commandBuilder("ambiguous") .argument(StringArgument.of("string")) ); - manager.command(manager.commandBuilder("ambiguous") + this.commandManager.command(this.commandManager.commandBuilder("ambiguous") .literal("literal")); - newTree(); + this.setup(); // Two literals (different names) and argument can co-exist, not ambiguous - manager.command(manager.commandBuilder("ambiguous") + this.commandManager.command(this.commandManager.commandBuilder("ambiguous") .literal("literal")); - manager.command(manager.commandBuilder("ambiguous") + this.commandManager.command(this.commandManager.commandBuilder("ambiguous") .literal("literal2")); - manager.command(manager.commandBuilder("ambiguous") + this.commandManager.command(this.commandManager.commandBuilder("ambiguous") .argument(IntegerArgument.of("integer"))); - newTree(); + this.setup(); // Two literals with the same name can not co-exist, causes 'duplicate command chains' error - manager.command(manager.commandBuilder("ambiguous") + this.commandManager.command(this.commandManager.commandBuilder("ambiguous") .literal("literal")); - Assertions.assertThrows(IllegalStateException.class, () -> - manager.command(manager.commandBuilder("ambiguous") + assertThrows(IllegalStateException.class, () -> + this.commandManager.command(this.commandManager.commandBuilder("ambiguous") .literal("literal"))); - newTree(); + this.setup(); } @Test void testLiteralRepeatingArgument() { // Build a command with a literal repeating - Command command = manager.commandBuilder("repeatingargscommand") + Command command = this.commandManager.commandBuilder("repeatingargscommand") .literal("repeat") .literal("middle") .literal("repeat") @@ -386,132 +454,140 @@ class CommandTreeTest { // Verify built command has the repeat argument twice List> args = command.getArguments(); - Assertions.assertEquals(4, args.size()); - Assertions.assertEquals("repeatingargscommand", args.get(0).getName()); - Assertions.assertEquals("repeat", args.get(1).getName()); - Assertions.assertEquals("middle", args.get(2).getName()); - Assertions.assertEquals("repeat", args.get(3).getName()); + assertThat(args.size()).isEqualTo(4);; + assertThat(args.get(0).getName()).isEqualTo("repeatingargscommand");; + assertThat(args.get(1).getName()).isEqualTo("repeat");; + assertThat(args.get(2).getName()).isEqualTo("middle");; + assertThat(args.get(3).getName()).isEqualTo("repeat");; // Register - manager.command(command); + this.commandManager.command(command); // If internally it drops repeating arguments, then it would register: // > /repeatingargscommand repeat middle // So check that we can register that exact command without an ambiguity exception - manager.command( - manager.commandBuilder("repeatingargscommand") - .literal("repeat") - .literal("middle") + this.commandManager.command( + this.commandManager.commandBuilder("repeatingargscommand") + .literal("repeat") + .literal("middle") ); } @Test void testAmbiguousLiteralOverridingArgument() { /* Build two commands for testing literals overriding variable arguments */ - manager.command( - manager.commandBuilder("literalwithvariable") - .argument(StringArgument.of("variable")) + this.commandManager.command( + this.commandManager.commandBuilder("literalwithvariable") + .argument(StringArgument.of("variable")) ); - manager.command( - manager.commandBuilder("literalwithvariable") - .literal("literal", "literalalias") + this.commandManager.command( + this.commandManager.commandBuilder("literalwithvariable") + .literal("literal", "literalalias") ); /* Try parsing as a variable, which should match the variable command */ - final Pair, Exception> variableResult = manager.getCommandTree().parse( - new CommandContext<>(new TestCommandSender(), manager), - new LinkedList<>(Arrays.asList("literalwithvariable", "argthatdoesnotmatch")) - ); - Assertions.assertNull(variableResult.getSecond()); - Assertions.assertEquals("literalwithvariable", - variableResult.getFirst().getArguments().get(0).getName()); - Assertions.assertEquals("variable", - variableResult.getFirst().getArguments().get(1).getName()); + final Pair, Exception> variableResult = this.commandManager.getCommandTree().parse( + new CommandContext<>(new TestCommandSender(), this.commandManager), + new LinkedList<>(Arrays.asList("literalwithvariable", "argthatdoesnotmatch")) + ); + assertThat(variableResult.getSecond()).isNull(); + assertThat(variableResult.getFirst().getArguments().get(0).getName()).isEqualTo("literalwithvariable");; + assertThat(variableResult.getFirst().getArguments().get(1).getName()).isEqualTo("variable");; /* Try parsing with the main name literal, which should match the literal command */ - final Pair, Exception> literalResult = manager.getCommandTree().parse( - new CommandContext<>(new TestCommandSender(), manager), + final Pair, Exception> literalResult = this.commandManager.getCommandTree().parse( + new CommandContext<>(new TestCommandSender(), this.commandManager), new LinkedList<>(Arrays.asList("literalwithvariable", "literal")) ); - Assertions.assertNull(literalResult.getSecond()); - Assertions.assertEquals("literalwithvariable", - literalResult.getFirst().getArguments().get(0).getName()); - Assertions.assertEquals("literal", - literalResult.getFirst().getArguments().get(1).getName()); + assertThat(literalResult.getSecond()).isNull(); + assertThat(literalResult.getFirst().getArguments().get(0).getName()).isEqualTo("literalwithvariable");; + assertThat(literalResult.getFirst().getArguments().get(1).getName()).isEqualTo("literal");; /* Try parsing with the alias of the literal, which should match the literal command */ - final Pair, Exception> literalAliasResult = manager.getCommandTree().parse( - new CommandContext<>(new TestCommandSender(), manager), + final Pair, Exception> literalAliasResult = this.commandManager.getCommandTree().parse( + new CommandContext<>(new TestCommandSender(), this.commandManager), new LinkedList<>(Arrays.asList("literalwithvariable", "literalalias")) ); - Assertions.assertNull(literalAliasResult.getSecond()); - Assertions.assertEquals("literalwithvariable", - literalAliasResult.getFirst().getArguments().get(0).getName()); - Assertions.assertEquals("literal", - literalAliasResult.getFirst().getArguments().get(1).getName()); + assertThat(literalAliasResult.getSecond()).isNull(); + assertThat(literalAliasResult.getFirst().getArguments().get(0).getName()).isEqualTo("literalwithvariable");; + assertThat(literalAliasResult.getFirst().getArguments().get(1).getName()).isEqualTo("literal");; } @Test void testDuplicateArgument() { + // Arrange final CommandArgument argument = StringArgument.of("test"); - manager.command(manager.commandBuilder("one").argument(argument)); - Assertions.assertThrows(IllegalArgumentException.class, () -> - manager.command(manager.commandBuilder("two").argument(argument))); - } + this.commandManager.command(this.commandManager.commandBuilder("one").argument(argument)); - @Test - void testFloats() { - manager.executeCommand(new TestCommandSender(), "float 0.0").join(); - manager.executeCommand(new TestCommandSender(), "float 100").join(); - } - - @Test - void testPreprocessors() { - manager.executeCommand(new TestCommandSender(), "preprocess abc").join(); - Assertions.assertThrows( - CompletionException.class, - () -> manager.executeCommand(new TestCommandSender(), "preprocess ab").join() + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> this.commandManager.command(this.commandManager.commandBuilder("two").argument(argument)) ); } + @Test + void testFloats() { + // Arrange + final CommandExecutionHandler executionHandler = mock(CommandExecutionHandler.class); + when(executionHandler.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null)); + + this.commandManager.command( + this.commandManager.commandBuilder("float") + .argument(FloatArgument.of("num")) + .handler(executionHandler) + .build() + ); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "float 0.0").join(); + this.commandManager.executeCommand(new TestCommandSender(), "float 100").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler, times(2)).executeFuture(contextArgumentCaptor.capture()); + + final Stream values = contextArgumentCaptor.getAllValues() + .stream() + .map(context -> context.get("num")); + assertThat(values).containsExactly(0.0f, 100f); + } + @Test void testOptionals() { - manager.executeCommand(new TestCommandSender(), "optionals").join(); + // Arrange + final CommandExecutionHandler executionHandler = mock(CommandExecutionHandler.class); + when(executionHandler.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null)); + + this.commandManager.command( + this.commandManager.commandBuilder("optionals") + .argument(StringArgument.optional("opt1")) + .argument(StringArgument.optional("opt2")) + .handler(executionHandler) + .build() + ); + + // Act + this.commandManager.executeCommand(new TestCommandSender(), "optionals").join(); + + // Assert + final ArgumentCaptor> contextArgumentCaptor = ArgumentCaptor.forClass( + CommandContext.class + ); + verify(executionHandler).executeFuture(contextArgumentCaptor.capture()); + + final CommandContext context = contextArgumentCaptor.getValue(); + assertThat(context.getOrDefault(SimpleCloudKey.of("opt1", TypeToken.get(String.class)), null)).isNull(); + assertThat(context.getOrDefault(SimpleCloudKey.of("opt2", TypeToken.get(String.class)), null)).isNull(); } - - public static final class SpecificCommandSender extends TestCommandSender { - - } - - - public static final class Vector2 { - - private final double x; - private final double y; - - private Vector2(final double x, final double y) { - this.x = x; - this.y = y; - } - - private double getX() { - return this.x; - } - - private double getY() { - return this.y; - } - - } - - - public enum FlagEnum { + enum FlagEnum { POTATO, CARROT, ONION, PROXI } - }