diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/SuggestionsProvider.java b/proxy/src/main/java/com/velocitypowered/proxy/command/SuggestionsProvider.java index 2de0503d4..e6af2d62b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/SuggestionsProvider.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/SuggestionsProvider.java @@ -30,6 +30,8 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.velocitypowered.api.command.Command; +import com.velocitypowered.api.command.CommandMeta; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.proxy.command.brigadier.VelocityArgumentCommandNode; import java.util.ArrayList; import java.util.Collection; @@ -283,12 +285,17 @@ final class SuggestionsProvider { * Parses the hint nodes under the given node, which is either an alias node of * a {@link Command} or another hint node. * + * The caller must check the requirements + * are satisfied by a given source prior to calling this method. + * *

The reader and context are not mutated by this method. * * @param node the node to parse * @param originalReader the input reader * @param contextSoFar the context, containing the alias node of the command * @return the parse results containing the parsed hint nodes + * @see VelocityCommandMeta#copyHints(CommandMeta) for the conditions under which the returned + * hints can be suggested to a {@link CommandSource}. */ private ParseResults parseHints(final CommandNode node, final StringReader originalReader, final CommandContextBuilder contextSoFar) { diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java index 3038ce766..b1fec4155 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.velocitypowered.proxy.command; import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; @@ -41,6 +58,57 @@ public class BrigadierCommandTests extends CommandTestSuite { assertEquals(1, callCount.get()); } + @Test + void testExecuteIgnoresAliasCase() { + final var callCount = new AtomicInteger(); + + final var node = LiteralArgumentBuilder + .literal("hello") + .executes(context -> { + assertEquals("hello", context.getInput()); + callCount.incrementAndGet(); + return 1; + }) + .build(); + manager.register(new BrigadierCommand(node)); + + assertHandled("Hello"); + assertEquals(1, callCount.get()); + } + + @Test + void testExecuteInputIsTrimmed() { + final var callCount = new AtomicInteger(); + + final var node = LiteralArgumentBuilder + .literal("hello") + .executes(context -> { + assertEquals("hello", context.getInput()); + callCount.incrementAndGet(); + return 1; + }) + .build(); + manager.register(new BrigadierCommand(node)); + + assertHandled(" hello"); + assertHandled(" hello"); + assertHandled("hello "); + assertHandled("hello "); + assertEquals(4, callCount.get()); + } + + @Test + void testExecuteAfterUnregisterForwards() { + final var node = LiteralArgumentBuilder + .literal("hello") + .executes(context -> fail()) + .build(); + manager.register(new BrigadierCommand(node)); + manager.unregister("hello"); + + assertForwarded("hello"); + } + @Test void testForwardsAndDoesNotExecuteImpermissibleAlias() { final var callCount = new AtomicInteger(); @@ -162,6 +230,17 @@ public class BrigadierCommandTests extends CommandTestSuite { // Suggestions + @Test + void testDoesNotSuggestAliasAfterUnregister() { + final var node = LiteralArgumentBuilder + .literal("hello") + .build(); + manager.register(new BrigadierCommand(node)); + manager.unregister("hello"); + + assertSuggestions(""); + } + @Test void testArgumentSuggestions() { final var node = LiteralArgumentBuilder diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandGraphInjectorTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandGraphInjectorTests.java index d0c3a9414..92a5b8b19 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandGraphInjectorTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandGraphInjectorTests.java @@ -22,7 +22,6 @@ import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal; import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -36,16 +35,13 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class CommandGraphInjectorTests { +public class CommandGraphInjectorTests extends CommandTestSuite { - private static final CommandSource SOURCE = MockCommandSource.INSTANCE; - - private VelocityCommandManager manager; private RootCommandNode dest; @BeforeEach void setUp() { - this.manager = CommandManagerTests.newManager(); + super.setUp(); this.dest = new RootCommandNode<>(); } @@ -53,7 +49,7 @@ public class CommandGraphInjectorTests { void testInjectInvocableCommand() { final var meta = manager.metaBuilder("hello").build(); manager.register(meta, (SimpleCommand) invocation -> fail()); - manager.getInjector().inject(dest, SOURCE); + manager.getInjector().inject(dest, source); // Preserves alias and arguments node final var expected = manager.getRoot(); @@ -73,14 +69,14 @@ public class CommandGraphInjectorTests { @Override public boolean hasPermission(final Invocation invocation) { - assertEquals(SOURCE, invocation.source()); + assertEquals(source, invocation.source()); assertEquals("hello", invocation.alias()); assertArrayEquals(new String[0], invocation.arguments()); callCount.incrementAndGet(); return false; } }); - manager.getInjector().inject(dest, SOURCE); + manager.getInjector().inject(dest, source); assertTrue(dest.getChildren().isEmpty()); assertEquals(1, callCount.get()); @@ -94,7 +90,7 @@ public class CommandGraphInjectorTests { .then(argument("count", integer())) .build(); manager.register(new BrigadierCommand(node)); - manager.getInjector().inject(dest, SOURCE); + manager.getInjector().inject(dest, source); assertEquals(node, dest.getChild("hello")); } @@ -113,7 +109,7 @@ public class CommandGraphInjectorTests { })) .build(); manager.register(new BrigadierCommand(registered)); - manager.getInjector().inject(dest, SOURCE); + manager.getInjector().inject(dest, source); final var expected = LiteralArgumentBuilder .literal("greet") @@ -131,7 +127,7 @@ public class CommandGraphInjectorTests { .build()) .build(); manager.register(new BrigadierCommand(registered)); - manager.getInjector().inject(dest, SOURCE); + manager.getInjector().inject(dest, source); final var expected = LiteralArgumentBuilder .literal("origin") @@ -158,7 +154,7 @@ public class CommandGraphInjectorTests { ) .build(); manager.register(new BrigadierCommand(registered)); - manager.getInjector().inject(dest, SOURCE); + manager.getInjector().inject(dest, source); final var expected = LiteralArgumentBuilder .literal("hello") @@ -181,7 +177,7 @@ public class CommandGraphInjectorTests { .then(literal("baz")) .build(); dest.addChild(original); - manager.getInjector().inject(dest, SOURCE); + manager.getInjector().inject(dest, source); assertEquals(registered, dest.getChild("foo")); } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandManagerTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandManagerTests.java index 1168a8a3f..62c9d40bc 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandManagerTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandManagerTests.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.velocitypowered.proxy.command; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -10,37 +27,11 @@ import com.velocitypowered.api.command.BrigadierCommand; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.RawCommand; import com.velocitypowered.api.command.SimpleCommand; -import com.velocitypowered.proxy.event.MockEventManager; -import com.velocitypowered.proxy.event.VelocityEventManager; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class CommandManagerTests { - - static final VelocityEventManager EVENT_MANAGER = new MockEventManager(); - - static { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - EVENT_MANAGER.shutdown(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - })); - } - - static VelocityCommandManager newManager() { - return new VelocityCommandManager(EVENT_MANAGER); - } - - private VelocityCommandManager manager; - - @BeforeEach - void setUp() { - this.manager = newManager(); - } +public class CommandManagerTests extends CommandTestSuite { // Registration @@ -156,6 +147,21 @@ public class CommandManagerTests { assertTrue(manager.hasCommand("foo")); } + // Execution + + @Test + void testExecuteUnknownAliasIsForwarded() { + assertForwarded(""); + assertForwarded("hello"); + } + + // Suggestions + + @Test + void testEmptyManagerSuggestNoAliases() { + assertSuggestions(""); + } + static final class DummyCommand implements SimpleCommand { static final DummyCommand INSTANCE = new DummyCommand(); diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandTestSuite.java b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandTestSuite.java index ff7748905..c69443c96 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandTestSuite.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandTestSuite.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.velocitypowered.proxy.command; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -5,25 +22,46 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.proxy.event.MockEventManager; +import com.velocitypowered.proxy.event.VelocityEventManager; import java.util.Arrays; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; abstract class CommandTestSuite { + private static VelocityEventManager eventManager; + protected VelocityCommandManager manager; protected final CommandSource source = MockCommandSource.INSTANCE; + @BeforeAll + static void beforeAll() { + eventManager = new MockEventManager(); + } + + @AfterAll + static void afterAll() { + try { + eventManager.shutdown(); + eventManager = null; + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + @BeforeEach void setUp() { - this.manager = CommandManagerTests.newManager(); + this.manager = new VelocityCommandManager(eventManager); } final void assertHandled(final String input) { - assertTrue(manager.executeAsync(source, input).join()); + assertTrue(manager.execute(source, input).join()); } final void assertForwarded(final String input) { - assertFalse(manager.executeAsync(source, input).join()); + assertFalse(manager.execute(source, input).join()); } final void assertSuggestions(final String input, final String... expectedSuggestions) { diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/OldCommandManagerTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/OldCommandManagerTests.java deleted file mode 100644 index 985d5ad47..000000000 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/OldCommandManagerTests.java +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Copyright (C) 2018 Velocity Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.velocitypowered.proxy.command; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.mojang.brigadier.arguments.IntegerArgumentType; -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; -import com.mojang.brigadier.tree.ArgumentCommandNode; -import com.mojang.brigadier.tree.CommandNode; -import com.mojang.brigadier.tree.LiteralCommandNode; -import com.velocitypowered.api.command.BrigadierCommand; -import com.velocitypowered.api.command.CommandMeta; -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.command.RawCommand; -import com.velocitypowered.api.command.SimpleCommand; -import com.velocitypowered.proxy.event.MockEventManager; -import com.velocitypowered.proxy.event.VelocityEventManager; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -@Disabled -public class OldCommandManagerTests { - - static VelocityCommandManager createManager() { - return new VelocityCommandManager(CommandManagerTests.EVENT_MANAGER); - } - - @Test - void testConstruction() { - VelocityCommandManager manager = createManager(); - assertFalse(manager.hasCommand("foo")); - assertTrue(manager.getRoot().getChildren().isEmpty()); - assertFalse(manager.execute(MockCommandSource.INSTANCE, "foo").join()); - assertFalse(manager.executeImmediately(MockCommandSource.INSTANCE, "bar").join()); - assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "").join().isEmpty()); - } - - @Test - void testBrigadierRegister() { - VelocityCommandManager manager = createManager(); - LiteralCommandNode node = LiteralArgumentBuilder - .literal("foo") - .build(); - BrigadierCommand command = new BrigadierCommand(node); - manager.register(command); - - assertEquals(node, command.getNode()); - assertTrue(manager.hasCommand("fOo")); - - LiteralCommandNode barNode = LiteralArgumentBuilder - .literal("bar") - .build(); - BrigadierCommand aliasesCommand = new BrigadierCommand(barNode); - CommandMeta meta = manager.createMetaBuilder(aliasesCommand) - .aliases("baZ") - .build(); - - assertEquals(ImmutableSet.of("bar", "baz"), meta.aliases()); - assertTrue(meta.hints().isEmpty()); - manager.register(meta, aliasesCommand); - assertTrue(manager.hasCommand("bAr")); - assertTrue(manager.hasCommand("Baz")); - } - - @Test - void testSimpleRegister() { - VelocityCommandManager manager = createManager(); - SimpleCommand command = new NoopSimpleCommand(); - - manager.register("Foo", command); - assertTrue(manager.hasCommand("foO")); - manager.unregister("fOo"); - assertFalse(manager.hasCommand("foo")); - assertFalse(manager.execute(MockCommandSource.INSTANCE, "foo").join()); - - manager.register("foo", command, "bAr", "BAZ"); - assertTrue(manager.hasCommand("bar")); - assertTrue(manager.hasCommand("bAz")); - } - - @Test - void testRawRegister() { - VelocityCommandManager manager = createManager(); - RawCommand command = new NoopRawCommand(); - - manager.register("foO", command, "BAR"); - assertTrue(manager.hasCommand("fOo")); - assertTrue(manager.hasCommand("bar")); - } - - @Test - void testBrigadierExecute() { - VelocityCommandManager manager = createManager(); - AtomicBoolean executed = new AtomicBoolean(false); - AtomicBoolean checkedRequires = new AtomicBoolean(false); - LiteralCommandNode node = LiteralArgumentBuilder - .literal("buy") - .executes(context -> { - assertEquals(MockCommandSource.INSTANCE, context.getSource()); - assertEquals("buy", context.getInput()); - executed.set(true); - return 1; - }) - .build(); - CommandNode quantityNode = RequiredArgumentBuilder - .argument("quantity", IntegerArgumentType.integer(12, 16)) - .requires(source -> { - assertEquals(MockCommandSource.INSTANCE, source); - checkedRequires.set(true); - return true; - }) - .executes(context -> { - int argument = IntegerArgumentType.getInteger(context, "quantity"); - assertEquals(14, argument); - executed.set(true); - return 1; - }) - .build(); - CommandNode productNode = RequiredArgumentBuilder - .argument("product", StringArgumentType.string()) - .requires(source -> { - checkedRequires.set(true); - return false; - }) - .executes(context -> fail("was executed")) - .build(); - quantityNode.addChild(productNode); - node.addChild(quantityNode); - manager.register(new BrigadierCommand(node)); - - assertTrue(manager.execute(MockCommandSource.INSTANCE, "buy ").join()); - assertTrue(executed.compareAndSet(true, false), "was executed"); - assertTrue(manager.executeImmediately(MockCommandSource.INSTANCE, "buy 14").join()); - assertTrue(checkedRequires.compareAndSet(true, false)); - assertTrue(executed.get()); - assertTrue(manager.execute(MockCommandSource.INSTANCE, "buy 9").join(), - "Invalid arg returns false"); - assertTrue(manager.executeImmediately(MockCommandSource.INSTANCE, "buy 12 bananas") - .join()); - assertTrue(checkedRequires.get()); - } - - @Test - void testSimpleExecute() { - VelocityCommandManager manager = createManager(); - AtomicBoolean executed = new AtomicBoolean(false); - SimpleCommand command = invocation -> { - assertEquals(MockCommandSource.INSTANCE, invocation.source()); - assertArrayEquals(new String[] {"bar", "254"}, invocation.arguments()); - executed.set(true); - }; - manager.register("foo", command); - - assertTrue(manager.execute(MockCommandSource.INSTANCE, "foo bar 254").join()); - assertTrue(executed.get()); - - SimpleCommand noPermsCommand = new SimpleCommand() { - @Override - public void execute(final Invocation invocation) { - fail("was executed"); - } - - @Override - public boolean hasPermission(final Invocation invocation) { - return false; - } - }; - - manager.register("dangerous", noPermsCommand, "veryDangerous"); - assertFalse(manager.execute(MockCommandSource.INSTANCE, "dangerous").join()); - assertFalse(manager.executeImmediately(MockCommandSource.INSTANCE, "verydangerous 123") - .join()); - } - - @Test - void testRawExecute() { - VelocityCommandManager manager = createManager(); - AtomicBoolean executed = new AtomicBoolean(false); - RawCommand command = new RawCommand() { - @Override - public void execute(final Invocation invocation) { - assertEquals(MockCommandSource.INSTANCE, invocation.source()); - assertEquals("lobby 23", invocation.arguments()); - executed.set(true); - } - }; - manager.register("sendMe", command); - - assertTrue(manager.executeImmediately(MockCommandSource.INSTANCE, "sendMe lobby 23") - .join()); - assertTrue(executed.compareAndSet(true, false)); - - RawCommand noArgsCommand = new RawCommand() { - @Override - public void execute(final Invocation invocation) { - assertEquals("", invocation.arguments()); - executed.set(true); - } - }; - manager.register("noargs", noArgsCommand); - - assertTrue(manager.executeImmediately(MockCommandSource.INSTANCE, "noargs").join()); - assertTrue(executed.get()); - assertTrue(manager.executeImmediately(MockCommandSource.INSTANCE, "noargs ").join()); - - RawCommand noPermsCommand = new RawCommand() { - @Override - public void execute(final Invocation invocation) { - fail("was executed"); - } - - @Override - public boolean hasPermission(final Invocation invocation) { - return false; - } - }; - - manager.register("sendThem", noPermsCommand); - assertFalse(manager.executeImmediately(MockCommandSource.INSTANCE, "sendThem foo") - .join()); - } - - @Test - void testSuggestions() { - VelocityCommandManager manager = createManager(); - - LiteralCommandNode brigadierNode = LiteralArgumentBuilder - .literal("brigadier") - .build(); - CommandNode nameNode = RequiredArgumentBuilder - .argument("name", StringArgumentType.string()) - .build(); - CommandNode numberNode = RequiredArgumentBuilder - .argument("quantity", IntegerArgumentType.integer()) - .suggests((context, builder) -> builder.suggest(2).suggest(3).buildFuture()) - .build(); - nameNode.addChild(numberNode); - brigadierNode.addChild(nameNode); - manager.register(new BrigadierCommand(brigadierNode)); - - SimpleCommand simpleCommand = new SimpleCommand() { - @Override - public void execute(final Invocation invocation) { - fail(); - } - - @Override - public List suggest(final Invocation invocation) { - switch (invocation.arguments().length) { - case 0: - return ImmutableList.of("foo", "bar"); - case 1: - return ImmutableList.of("123"); - default: - return ImmutableList.of(); - } - } - }; - manager.register("simple", simpleCommand); - - RawCommand rawCommand = new RawCommand() { - @Override - public void execute(final Invocation invocation) { - fail(); - } - - @Override - public List suggest(final Invocation invocation) { - switch (invocation.arguments()) { - case "": - return ImmutableList.of("foo", "baz"); - case "foo ": - return ImmutableList.of("2", "3", "5", "7"); - case "bar ": - return ImmutableList.of("11", "13", "17"); - default: - return ImmutableList.of(); - } - } - }; - manager.register("raw", rawCommand); - - assertEquals( - ImmutableList.of("brigadier", "raw", "simple"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "").join(), - "literals are in alphabetical order"); - assertEquals( - ImmutableList.of("brigadier"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "briga").join()); - assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "brigadier") - .join().isEmpty()); - assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "brigadier ") - .join().isEmpty()); - assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "brigadier foo") - .join().isEmpty()); - assertEquals( - ImmutableList.of("2", "3"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "brigadier foo ").join()); - assertEquals( - ImmutableList.of("bar", "foo"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "simple ").join()); - assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "simple") - .join().isEmpty()); - assertEquals( - ImmutableList.of("123"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "simPle foo").join()); - assertEquals( - ImmutableList.of("baz", "foo"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "raw ").join()); - assertEquals( - ImmutableList.of("2", "3", "5", "7"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "raw foo ").join()); - assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "raw foo") - .join().isEmpty()); - assertEquals( - ImmutableList.of("11", "13", "17"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "rAW bar ").join()); - } - - @Test - void testBrigadierSuggestionPermissions() { - VelocityCommandManager manager = createManager(); - LiteralCommandNode manageNode = LiteralArgumentBuilder - .literal("manage") - .requires(source -> false) - .build(); - CommandNode idNode = RequiredArgumentBuilder - .argument("id", IntegerArgumentType.integer(0)) - .suggests((context, builder) -> fail("called suggestion builder")) - .build(); - manageNode.addChild(idNode); - manager.register(new BrigadierCommand(manageNode)); - - // Brigadier doesn't call the children predicate when requesting suggestions. - // However, it won't query children if the source doesn't pass the parent - // #requires predicate. - assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "manage ") - .join().isEmpty()); - } - - @Test - @Disabled - void testHinting() { - VelocityCommandManager manager = createManager(); - AtomicBoolean executed = new AtomicBoolean(false); - AtomicBoolean calledSuggestionProvider = new AtomicBoolean(false); - AtomicReference expectedArgs = new AtomicReference<>(); - RawCommand command = new RawCommand() { - @Override - public void execute(final Invocation invocation) { - assertEquals(expectedArgs.get(), invocation.arguments()); - executed.set(true); - } - - @Override - public List suggest(final Invocation invocation) { - return ImmutableList.of("raw"); - } - }; - - CommandNode barHint = LiteralArgumentBuilder - .literal("bar") - .executes(context -> fail("hints don't get executed")) - .build(); - ArgumentCommandNode numberArg = RequiredArgumentBuilder - .argument("number", IntegerArgumentType.integer()) - .suggests((context, builder) -> { - calledSuggestionProvider.set(true); - return builder.suggest("456").buildFuture(); - }) - .build(); - barHint.addChild(numberArg); - CommandNode bazHint = LiteralArgumentBuilder - .literal("baz") - .build(); - CommandMeta meta = manager.createMetaBuilder("foo") - .aliases("foo2") - .hint(barHint) - .hint(bazHint) - .build(); - manager.register(meta, command); - - expectedArgs.set("notBarOrBaz"); - assertTrue(manager.execute(MockCommandSource.INSTANCE, "foo notBarOrBaz").join()); - assertTrue(executed.compareAndSet(true, false)); - expectedArgs.set("anotherArg 123"); - assertTrue(manager.execute(MockCommandSource.INSTANCE, "Foo2 anotherArg 123").join()); - assertTrue(executed.compareAndSet(true, false)); - expectedArgs.set("bar"); - assertTrue(manager.execute(MockCommandSource.INSTANCE, "foo bar").join()); - assertTrue(executed.compareAndSet(true, false)); - expectedArgs.set("bar 123"); - assertTrue(manager.execute(MockCommandSource.INSTANCE, "foo2 bar 123").join()); - assertTrue(executed.compareAndSet(true, false)); - - assertEquals(ImmutableList.of("bar", "baz", "raw"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "foo ").join()); - assertFalse(calledSuggestionProvider.get()); - assertEquals(ImmutableList.of("456"), - manager.offerSuggestions(MockCommandSource.INSTANCE, "foo bar ").join()); - assertTrue(calledSuggestionProvider.compareAndSet(true, false)); - assertEquals(ImmutableList.of(), - manager.offerSuggestions(MockCommandSource.INSTANCE, "foo2 baz ").join()); - } - - @Test - void testSuggestionPermissions() throws ExecutionException, InterruptedException { - VelocityCommandManager manager = createManager(); - RawCommand rawCommand = new RawCommand() { - @Override - public void execute(final Invocation invocation) { - fail("The Command should not be executed while testing suggestions"); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.arguments().length() > 0; - } - - @Override - public List suggest(final Invocation invocation) { - return ImmutableList.of("suggestion"); - } - }; - - manager.register("foo", rawCommand); - - assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "foo").get().isEmpty()); - assertFalse(manager.offerSuggestions(MockCommandSource.INSTANCE, "foo bar").get().isEmpty()); - - SimpleCommand oldCommand = new SimpleCommand() { - @Override - public void execute(Invocation invocation) { - fail("The Command should not be executed while testing suggestions"); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.arguments().length > 0; - } - - @Override - public List suggest(Invocation invocation) { - return ImmutableList.of("suggestion"); - } - }; - - manager.register("bar", oldCommand); - - assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "bar").get().isEmpty()); - assertFalse(manager.offerSuggestions(MockCommandSource.INSTANCE, "bar foo").get().isEmpty()); - } - - static class NoopSimpleCommand implements SimpleCommand { - @Override - public void execute(final Invocation invocation) { - - } - } - - static class NoopRawCommand implements RawCommand { - @Override - public void execute(final Invocation invocation) { - - } - } -} diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/RawCommandTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/RawCommandTests.java index 3f8ff1bfe..2b03ace8d 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/RawCommandTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/RawCommandTests.java @@ -1,12 +1,33 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.velocitypowered.proxy.command; +import static com.mojang.brigadier.arguments.StringArgumentType.word; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import com.google.common.collect.ImmutableList; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.spotify.futures.CompletableFutures; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.RawCommand; import com.velocitypowered.api.command.SimpleCommand; import java.util.Collections; @@ -36,6 +57,47 @@ public class RawCommandTests extends CommandTestSuite { assertEquals(1, callCount.get()); } + @Test + void testExecuteIgnoresAliasCase() { + final var callCount = new AtomicInteger(); + + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, (RawCommand) invocation -> { + assertEquals("hello", invocation.alias()); + callCount.incrementAndGet(); + }); + + assertHandled("Hello"); + assertEquals(1, callCount.get()); + } + + @Test + void testExecuteInputIsTrimmed() { + final var callCount = new AtomicInteger(); + + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, (RawCommand) invocation -> { + assertEquals("hello", invocation.alias()); + assertEquals("", invocation.arguments()); + callCount.incrementAndGet(); + }); + + assertHandled(" hello"); + assertHandled(" hello"); + assertHandled("hello "); + assertHandled("hello "); + assertEquals(4, callCount.get()); + } + + @Test + void testExecuteAfterUnregisterForwards() { + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, (RawCommand) invocation -> fail()); + manager.unregister("hello"); + + assertForwarded("hello"); + } + @Test void testForwardsAndDoesNotExecuteImpermissibleAlias() { final var callCount = new AtomicInteger(); @@ -125,6 +187,25 @@ public class RawCommandTests extends CommandTestSuite { }); } + @Test + void testDoesNotSuggestAliasAfterUnregister() { + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + return fail(); + } + }); + manager.unregister("hello"); + + assertSuggestions(""); + } + @Test void testSuggestsArgumentsAfterAlias() { final var meta = manager.metaBuilder("hello").build(); @@ -301,4 +382,105 @@ public class RawCommandTests extends CommandTestSuite { assertThrows(CompletionException.class, () -> manager.offerSuggestions(source, "hello ").join()); } + + // Hinting + + // Even if the following 2 cases look really similar, they test + // different parts of SuggestionsProvider. + @Test + void testDoesNotSuggestHintIfImpermissibleAlias() { + final var hint = LiteralArgumentBuilder + .literal("hint") + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + return false; + } + }); + + assertSuggestions("hello "); + } + + @Test + void testDoesNotSuggestHintIfImpermissibleArguments() { + final var hint = LiteralArgumentBuilder + .literal("hint") + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + return false; + } + }); + + assertSuggestions("hello hin"); + } + + @Test + void testSuggestsMergesIgnoringHintsWhoseCustomSuggestionProviderFutureCompletesExceptionally() { + final var hint = RequiredArgumentBuilder + .argument("hint", word()) + .suggests((context, builder) -> + CompletableFutures.exceptionallyCompletedFuture(new RuntimeException())) + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + return ImmutableList.of("world"); + } + }); + + assertSuggestions("hello ", "world"); + } + + @Test + void testSuggestsMergesIgnoringHintsWhoseCustomSuggestionProviderThrows() { + final var hint = RequiredArgumentBuilder + .argument("hint", word()) + .suggests((context, builder) -> { + throw new RuntimeException(); + }) + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, new RawCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + return ImmutableList.of("world"); + } + }); + + assertSuggestions("hello ", "world"); + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/SimpleCommandTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/SimpleCommandTests.java index e4ccec57d..644b6a31e 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/SimpleCommandTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/SimpleCommandTests.java @@ -1,12 +1,33 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.velocitypowered.proxy.command; +import static com.mojang.brigadier.arguments.StringArgumentType.word; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import com.google.common.collect.ImmutableList; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.spotify.futures.CompletableFutures; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import java.util.Collections; import java.util.List; @@ -35,6 +56,47 @@ public class SimpleCommandTests extends CommandTestSuite { assertEquals(1, callCount.get()); } + @Test + void testExecuteIgnoresAliasCase() { + final var callCount = new AtomicInteger(); + + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, (SimpleCommand) invocation -> { + assertEquals("hello", invocation.alias()); + callCount.incrementAndGet(); + }); + + assertHandled("Hello"); + assertEquals(1, callCount.get()); + } + + @Test + void testExecuteInputIsTrimmed() { + final var callCount = new AtomicInteger(); + + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, (SimpleCommand) invocation -> { + assertEquals("hello", invocation.alias()); + assertArrayEquals(new String[0], invocation.arguments()); + callCount.incrementAndGet(); + }); + + assertHandled(" hello"); + assertHandled(" hello"); + assertHandled("hello "); + assertHandled("hello "); + assertEquals(4, callCount.get()); + } + + @Test + void testExecuteAfterUnregisterForwards() { + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, (SimpleCommand) invocation -> fail()); + manager.unregister("hello"); + + assertForwarded("hello"); + } + @Test void testForwardsAndDoesNotExecuteImpermissibleAlias() { final var callCount = new AtomicInteger(); @@ -124,6 +186,25 @@ public class SimpleCommandTests extends CommandTestSuite { }); } + @Test + void testDoesNotSuggestAliasAfterUnregister() { + final var meta = manager.metaBuilder("hello").build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + return fail(); + } + }); + manager.unregister("hello"); + + assertSuggestions(""); + } + @Test void testSuggestsArgumentsAfterAlias() { final var meta = manager.metaBuilder("hello").build(); @@ -300,4 +381,105 @@ public class SimpleCommandTests extends CommandTestSuite { assertThrows(CompletionException.class, () -> manager.offerSuggestions(source, "hello ").join()); } + + // Hinting + + // Even if the following 2 cases look really similar, they test + // different parts of SuggestionsProvider. + @Test + void testDoesNotSuggestHintIfImpermissibleAlias() { + final var hint = LiteralArgumentBuilder + .literal("hint") + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + return false; + } + }); + + assertSuggestions("hello "); + } + + @Test + void testDoesNotSuggestHintIfImpermissibleArguments() { + final var hint = LiteralArgumentBuilder + .literal("hint") + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public boolean hasPermission(final Invocation invocation) { + return false; + } + }); + + assertSuggestions("hello hin"); + } + + @Test + void testSuggestsMergesIgnoringHintsWhoseCustomSuggestionProviderFutureCompletesExceptionally() { + final var hint = RequiredArgumentBuilder + .argument("hint", word()) + .suggests((context, builder) -> + CompletableFutures.exceptionallyCompletedFuture(new RuntimeException())) + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + return ImmutableList.of("world"); + } + }); + + assertSuggestions("hello ", "world"); + } + + @Test + void testSuggestsMergesIgnoringHintsWhoseCustomSuggestionProviderThrows() { + final var hint = RequiredArgumentBuilder + .argument("hint", word()) + .suggests((context, builder) -> { + throw new RuntimeException(); + }) + .build(); + final var meta = manager.metaBuilder("hello") + .hint(hint) + .build(); + manager.register(meta, new SimpleCommand() { + @Override + public void execute(final Invocation invocation) { + fail(); + } + + @Override + public List suggest(final Invocation invocation) { + return ImmutableList.of("world"); + } + }); + + assertSuggestions("hello ", "world"); + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java index 0d13ec3c7..30218528f 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/SuggestionsProviderTests.java @@ -18,22 +18,16 @@ package com.velocitypowered.proxy.command; import static com.mojang.brigadier.arguments.StringArgumentType.word; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import com.google.common.collect.ImmutableList; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.spotify.futures.CompletableFutures; -import com.velocitypowered.api.command.BrigadierCommand; import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.RawCommand; -import com.velocitypowered.api.command.SimpleCommand; import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import org.junit.jupiter.api.Test; /** @@ -196,22 +190,24 @@ public class SuggestionsProviderTests extends CommandTestSuite { assertSuggestions("foo ", "bar", "baz", "qux"); assertSuggestions("foo bar", "baz", "qux"); - assertSuggestions("foo baz", "bar", "qux"); + assertSuggestions("foo baz", "qux"); } + // Hints are suggested iff the source can use the given arguments; see + // VelocityCommandMeta#copyHints. @Test - // This doesn't make much sense, but emulates Brigadier behavior - void testSuggestsImpermissibleHint() { - final var hint = LiteralArgumentBuilder - .literal("hint") - .requires(source1 -> false) + void testSuggestIgnoresHintRequirementPredicateResults() { + final var hint = RequiredArgumentBuilder + .argument("hint", word()) + .requires(source1 -> fail()) + .suggests((context, builder) -> builder.suggest("suggestion").buildFuture()) .build(); final var meta = manager.createMetaBuilder("hello") .hint(hint) .build(); manager.register(meta, NoSuggestionsCommand.INSTANCE); - assertSuggestions("hello ", "hint"); + assertSuggestions("hello ", "suggestion"); } @Test @@ -245,51 +241,6 @@ public class SuggestionsProviderTests extends CommandTestSuite { assertSuggestions("hello "); } - @Test - void testSuggestCompletesExceptionallyIfHintRequirementPredicateThrows() { - final var hint = RequiredArgumentBuilder - .argument("hint", word()) - .requires(source1 -> { - throw new RuntimeException(); - }) - .suggests((context, builder) -> fail()) - .build(); - final var meta = manager.createMetaBuilder("hello") - .hint(hint) - .build(); - manager.register(meta, NoSuggestionsCommand.INSTANCE); - - assertThrows(CompletionException.class, () -> - manager.offerSuggestions(source, "hello ").join()); - } - - /* - - @Test - void testSuggestionMergingIgnoresExceptionallyCompletedSuggestionFutures() { - final var hint = RequiredArgumentBuilder - .argument("hint", word()) - .suggests((context, builder) -> - CompletableFutures.exceptionallyCompletedFuture(new RuntimeException())) - .build(); - final var meta = manager.createMetaBuilder("hello") - .hint(hint) - .build(); - manager.register(meta, new RawCommand() { - @Override - public void execute(final Invocation invocation) { - fail(); - } - - @Override - public List suggest(final Invocation invocation) { - return ImmutableList.of("world"); - } - }); - - assertSuggestions("hello ", "world"); - }*/ - static final class NoSuggestionsCommand implements RawCommand { static final NoSuggestionsCommand INSTANCE = new NoSuggestionsCommand();