diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml new file mode 100644 index 0000000..c4aeffc --- /dev/null +++ b/.github/workflows/test-all.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main, develop ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Make Gradle wrapper executable + run: chmod +x gradlew + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 + + - name: Cache Gradle + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*','**/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle- + + - name: Build & test + run: ./gradlew clean test testAll --no-daemon + + - name: Upload test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + **/build/test-results/**/*.xml + **/build/reports/tests/** diff --git a/build.gradle b/build.gradle index 30890b7..ed4bb89 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,7 @@ +plugins { + id 'com.adarshr.test-logger' version '4.0.0' +} + allprojects { group = 'fr.traqueur.commands' version = property('version') @@ -6,10 +10,49 @@ allprojects { plugin 'java-library' } + tasks.withType(JavaCompile).configureEach { + options.compilerArgs += ['-nowarn'] + } + repositories { mavenCentral() maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } } -} \ No newline at end of file +} + +subprojects { + if (!project.name.contains('test-plugin')) { + + apply plugin: 'com.adarshr.test-logger' + + dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'org.mockito:mockito-core:5.3.1' + } + test { + useJUnitPlatform() + jvmArgs += ['-XX:+EnableDynamicAgentLoading'] + reports { + html.required.set(true) + junitXml.required.set(true) + } + + testLogging { + events("passed", "skipped", "failed") + exceptionFormat "full" + } + } + } +} + +tasks.register("testAll") { + group = "verification" + description = "Execute tous les tests des sous-projets qui ont une tâche 'test'" + + // Déclare la dépendance vers chaque tâche 'test' de chaque sous-projet + dependsOn subprojects + .findAll { it.tasks.findByName('test') != null } + .collect { it.tasks.named('test') } +} diff --git a/core/build.gradle b/core/build.gradle index 6c96b33..0b8a4fb 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -11,7 +11,7 @@ java { def generatedResourcesDir = "$buildDir/generated-resources" -task generateCommandsProperties { +tasks.register('generateCommandsProperties') { doLast { def outputDir = file("$generatedResourcesDir") outputDir.mkdirs() diff --git a/core/src/main/java/fr/traqueur/commands/api/Arguments.java b/core/src/main/java/fr/traqueur/commands/api/Arguments.java index 525330c..5226f18 100644 --- a/core/src/main/java/fr/traqueur/commands/api/Arguments.java +++ b/core/src/main/java/fr/traqueur/commands/api/Arguments.java @@ -323,6 +323,7 @@ public Optional getAs(String argument, Class typeRef) { throw new NoGoodTypeArgumentException(); } } catch (NoGoodTypeArgumentException e) { + logger.error("The argument " + argument + " is not the good type."); return Optional.empty(); } diff --git a/core/src/main/java/fr/traqueur/commands/api/Command.java b/core/src/main/java/fr/traqueur/commands/api/Command.java index 85fe236..f8739db 100644 --- a/core/src/main/java/fr/traqueur/commands/api/Command.java +++ b/core/src/main/java/fr/traqueur/commands/api/Command.java @@ -497,6 +497,10 @@ public String generateDefaultUsage(CommandPlatform platform, S sender, Stri StringBuilder usage = new StringBuilder(); usage.append("/"); Arrays.stream(label.split("\\.")).forEach(s -> usage.append(s).append(" ")); + //remove the last space + if(this.args.isEmpty() && this.optionalArgs.isEmpty()) { + usage.deleteCharAt(usage.length() - 1); + } StringBuilder firstArg = new StringBuilder(); this.getSubcommands() @@ -511,8 +515,11 @@ public String generateDefaultUsage(CommandPlatform platform, S sender, Stri } usage.append(this.getArgs().stream().map(argument -> "<" + argument.arg() + ">").collect(Collectors.joining(" "))); - usage.append(" "); - usage.append(this.getOptinalArgs().stream().map(argument -> "[" + argument.arg() + "]").collect(Collectors.joining(" "))); + if (!this.getOptinalArgs().isEmpty()) { + usage.append(" "); + usage.append(this.getOptinalArgs().stream().map(argument -> "[" + argument.arg() + "]").collect(Collectors.joining(" "))); + } + return usage.toString(); } diff --git a/core/src/main/java/fr/traqueur/commands/api/exceptions/ArgsWithInfiniteArgumentException.java b/core/src/main/java/fr/traqueur/commands/api/exceptions/ArgsWithInfiniteArgumentException.java index f3d1a51..16d5b1f 100644 --- a/core/src/main/java/fr/traqueur/commands/api/exceptions/ArgsWithInfiniteArgumentException.java +++ b/core/src/main/java/fr/traqueur/commands/api/exceptions/ArgsWithInfiniteArgumentException.java @@ -10,6 +10,6 @@ public class ArgsWithInfiniteArgumentException extends Exception { * @param optional if the argument is optional */ public ArgsWithInfiniteArgumentException(boolean optional) { - super((optional ? "Optional arguments" : "Arguments") + "cannot follow infinite arguments."); + super((optional ? "Optional arguments" : "Arguments") + " cannot follow infinite arguments."); } } diff --git a/core/src/main/java/fr/traqueur/commands/api/updater/Updater.java b/core/src/main/java/fr/traqueur/commands/api/updater/Updater.java index dbf7771..a8cb5b1 100644 --- a/core/src/main/java/fr/traqueur/commands/api/updater/Updater.java +++ b/core/src/main/java/fr/traqueur/commands/api/updater/Updater.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.Properties; @@ -13,6 +14,26 @@ */ public class Updater { + private static final String VERSION_PROPERTY_FILE = "commands.properties"; + private static URL URL_LATEST_RELEASE; + private static Logger LOGGER = Logger.getLogger("CommandsAPI"); + + static { + try { + URL_LATEST_RELEASE = URI.create("https://api.github.com/repos/Traqueur-dev/CommandsAPI/releases/latest").toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public static void setUrlLatestRelease(URL URL_LATEST_RELEASE) { + Updater.URL_LATEST_RELEASE = URL_LATEST_RELEASE; + } + + public static void setLogger(Logger LOGGER) { + Updater.LOGGER = LOGGER; + } + /** * Private constructor to prevent instantiation */ @@ -23,7 +44,7 @@ private Updater() {} */ public static void checkUpdates() { if(!Updater.isUpToDate()) { - Logger.getLogger("CommandsAPI").warning("The framework is not up to date, the latest version is " + Updater.fetchLatestVersion()); + LOGGER.warning("The framework is not up to date, the latest version is " + Updater.fetchLatestVersion()); } } @@ -34,7 +55,7 @@ public static void checkUpdates() { public static String getVersion() { Properties prop = new Properties(); try { - prop.load(Updater.class.getClassLoader().getResourceAsStream("commands.properties")); + prop.load(Updater.class.getClassLoader().getResourceAsStream(VERSION_PROPERTY_FILE)); return prop.getProperty("version"); } catch (IOException e) { throw new RuntimeException(e); @@ -60,8 +81,7 @@ public static boolean isUpToDate() { */ public static String fetchLatestVersion() { try { - URL url = URI.create("https://api.github.com/repos/Traqueur-dev/CommandsAPI/releases/latest").toURL(); - String responseString = getString(url); + String responseString = getString(); int tagNameIndex = responseString.indexOf("\"tag_name\""); int start = responseString.indexOf('\"', tagNameIndex + 10) + 1; int end = responseString.indexOf('\"', start); @@ -75,8 +95,8 @@ public static String fetchLatestVersion() { * Get the latest version of the plugin * @return The latest version of the plugin */ - private static String getString(URL url) throws IOException { - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + private static String getString() throws IOException { + HttpURLConnection connection = (HttpURLConnection) Updater.URL_LATEST_RELEASE.openConnection(); connection.setRequestMethod("GET"); StringBuilder response = new StringBuilder(); diff --git a/core/src/main/java/fr/traqueur/commands/impl/arguments/BooleanArgument.java b/core/src/main/java/fr/traqueur/commands/impl/arguments/BooleanArgument.java index 8bcc4de..e651402 100644 --- a/core/src/main/java/fr/traqueur/commands/impl/arguments/BooleanArgument.java +++ b/core/src/main/java/fr/traqueur/commands/impl/arguments/BooleanArgument.java @@ -22,6 +22,9 @@ public BooleanArgument() { @Override public Boolean apply(String s) { + if(s == null || s.isEmpty()) { + return null; + } if(s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false")) { return Boolean.parseBoolean(s); } diff --git a/core/src/main/java/fr/traqueur/commands/impl/arguments/DoubleArgument.java b/core/src/main/java/fr/traqueur/commands/impl/arguments/DoubleArgument.java index 8c82f39..c355e57 100644 --- a/core/src/main/java/fr/traqueur/commands/impl/arguments/DoubleArgument.java +++ b/core/src/main/java/fr/traqueur/commands/impl/arguments/DoubleArgument.java @@ -20,6 +20,9 @@ public DoubleArgument() {} */ @Override public Double apply(String input) { + if (input == null || input.isEmpty()) { + return null; + } try { return Double.valueOf(input); } catch (NumberFormatException e){ diff --git a/core/src/main/java/fr/traqueur/commands/impl/arguments/EnumArgument.java b/core/src/main/java/fr/traqueur/commands/impl/arguments/EnumArgument.java index 6af62c2..175c5c9 100644 --- a/core/src/main/java/fr/traqueur/commands/impl/arguments/EnumArgument.java +++ b/core/src/main/java/fr/traqueur/commands/impl/arguments/EnumArgument.java @@ -21,6 +21,9 @@ public EnumArgument(Class clazz) { @Override public Enum apply(String s) { + if (s == null || s.isEmpty()) { + return null; + } try { return Enum.valueOf(clazz, s); } catch (IllegalArgumentException e) { diff --git a/core/src/main/java/fr/traqueur/commands/impl/arguments/IntegerArgument.java b/core/src/main/java/fr/traqueur/commands/impl/arguments/IntegerArgument.java index 7a66946..9d82234 100644 --- a/core/src/main/java/fr/traqueur/commands/impl/arguments/IntegerArgument.java +++ b/core/src/main/java/fr/traqueur/commands/impl/arguments/IntegerArgument.java @@ -21,6 +21,9 @@ public IntegerArgument() {} */ @Override public Integer apply(String input) { + if (input == null || input.isEmpty()) { + return null; + } try { return Integer.valueOf(input); } catch (NumberFormatException e) { diff --git a/core/src/main/java/fr/traqueur/commands/impl/arguments/LongArgument.java b/core/src/main/java/fr/traqueur/commands/impl/arguments/LongArgument.java index b604a73..d19d1f4 100644 --- a/core/src/main/java/fr/traqueur/commands/impl/arguments/LongArgument.java +++ b/core/src/main/java/fr/traqueur/commands/impl/arguments/LongArgument.java @@ -21,6 +21,9 @@ public LongArgument() {} */ @Override public Long apply(String input) { + if (input == null || input.isEmpty()) { + return null; + } try { return Long.valueOf(input); } catch (NumberFormatException e) { diff --git a/core/src/test/java/fr/traqueur/commands/api/ArgumentsTest.java b/core/src/test/java/fr/traqueur/commands/api/ArgumentsTest.java new file mode 100644 index 0000000..3798593 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/api/ArgumentsTest.java @@ -0,0 +1,119 @@ +package fr.traqueur.commands.api; +import fr.traqueur.commands.impl.logging.InternalLogger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +class ArgumentsTest { + + private InternalLogger logger; + private Arguments args; + + @BeforeEach + void setUp() { + logger = Mockito.mock(InternalLogger.class); + args = new Arguments(logger); + } + + @Test + void testIntCast_validAndInvalid() { + args.add("num", String.class, "42"); + assertEquals(42, args.getAsInt("num", 0)); + args.add("bad", String.class, "abc"); + assertEquals(0, args.getAsInt("bad", 0)); + } + + @Test + void testDoubleCast_validAndInvalid() { + args.add("d", String.class, "3.14"); + assertEquals(3.14, args.getAsDouble("d", 0.0)); + args.add("badD", String.class, "pi"); + assertEquals(0.0, args.getAsDouble("badD", 0.0)); + } + + @Test + void testBooleanCast() { + args.add("b1", String.class, "true"); + args.add("b2", String.class, "FALSE"); + assertTrue(args.getAsBoolean("b1", false)); + assertFalse(args.getAsBoolean("b2", true)); + } + + @Test + void testStringCast_default() { + assertEquals("def", args.getAsString("missing", "def")); + args.add("s", String.class, "hello"); + assertEquals("hello", args.getAsString("s", "cfg")); + } + + @Test + void testLongFloatShortByteChar() { + args.add("L", String.class, "1234567890123"); + assertEquals(1234567890123L, args.getAsLong("L", 0L)); + args.add("f", String.class, "2.5"); + assertEquals(2.5f, args.getAsFloat("f", 0f)); + args.add("sh", String.class, "7"); + assertEquals((short)7, args.getAsShort("sh", (short)0)); + args.add("by", String.class, "8"); + assertEquals((byte)8, args.getAsByte("by", (byte)0)); + args.add("c", String.class, "z"); + assertEquals('z', args.getAsChar("c", 'x')); + } + + @Test + void testOptionalPresentAndEmpty() { + args.add("opt", String.class, "val"); + Optional optVal = args.getAsString("opt"); + assertTrue(optVal.isPresent()); + assertEquals("val", optVal.get()); + assertFalse(args.getAsInt("none").isPresent()); + } + + @Test + void testGetGeneric_andErrorLogging() { + args.add("gen", Integer.class, 5); + String wrong = args.getAs("gen", String.class, "def"); + assertEquals("def", wrong); + } + + @Test + void testGetThrowsArgumentNotExistLogged() { + assertNull(args.get("xxx")); + verify(logger).error(contains("xxx")); + } + + @Test + void testInfiniteArgsBehavior() { + args.add("all", String.class, "Infinite arguments test"); + String allArgs = args.get("all"); + assertNotNull(allArgs); + assertEquals("Infinite arguments test", allArgs); + } + + @Test + void getAs_logsErrorWhenWrongType() { + InternalLogger mockLogger = Mockito.mock(InternalLogger.class); + Arguments args = new Arguments(mockLogger); + args.add("num", Integer.class, 123); + Optional result = args.getAs("num", String.class); + assertFalse(result.isPresent()); + verify(mockLogger).error(contains("The argument num is not the good type.")); + } + + @Test + void getOptional_onEmptyMapReturnsEmptyWithoutError() { + InternalLogger mockLogger = Mockito.mock(InternalLogger.class); + Arguments args = new Arguments(mockLogger); + Optional opt = args.getOptional("anything"); + assertFalse(opt.isPresent()); + verify(mockLogger, never()).error(anyString()); + } +} \ No newline at end of file diff --git a/core/src/test/java/fr/traqueur/commands/api/CommandManagerTest.java b/core/src/test/java/fr/traqueur/commands/api/CommandManagerTest.java new file mode 100644 index 0000000..f0eb688 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/api/CommandManagerTest.java @@ -0,0 +1,192 @@ +package fr.traqueur.commands.api; + +import fr.traqueur.commands.api.arguments.TabCompleter; +import fr.traqueur.commands.api.exceptions.ArgumentIncorrectException; +import fr.traqueur.commands.api.exceptions.TypeArgumentNotExistException; +import fr.traqueur.commands.impl.logging.InternalLogger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.verify; + +class CommandManagerTest { + + private InternalLogger logger; + private CommandManager manager; + private FakePlatform platform; + + static class DummyCommand extends Command { + DummyCommand() { super(null, "dummy"); } + DummyCommand(String name) { super(null, name); } + @Override public void execute(String sender, Arguments args) {} + } + + static class FakePlatform implements CommandPlatform { + List added = new ArrayList<>(); + @Override public Object getPlugin() { return null; } + @Override public void injectManager(CommandManager cm) {} + @Override public java.util.logging.Logger getLogger() { return java.util.logging.Logger.getAnonymousLogger(); } + @Override public boolean hasPermission(String sender, String permission) { return true; } + @Override public void addCommand(Command command, String label) { added.add(label); } + @Override public void removeCommand(String label, boolean sub) {} + } + + @BeforeEach + void setUp() { + platform = new FakePlatform(); + manager = new CommandManager(platform) {}; + platform.injectManager(manager); + logger = Mockito.mock(InternalLogger.class); + manager.setLogger(logger); + } + + @Test + void testInfiniteArgsParsing() throws Exception { + Command cmd = new Command(null, "test") { + @Override + public void execute(String sender, Arguments arguments) {} + }; + cmd.setManager(manager); + cmd.addArgs("rest:infinite"); + + String[] input = {"one", "two", "three", "four"}; + Arguments args = manager.parse(cmd, input); + Object rest = args.get("rest"); + assertInstanceOf(String.class, rest); + assertNotNull(rest); + assertEquals("one two three four", rest); + } + + @Test + void testInfiniteArgsStopsFurtherParsing() throws Exception { + Command cmd = new DummyCommand(); + cmd.setManager(manager); + cmd.addArgs("first", String.class); + cmd.addArgs("rest:infinite"); + + String[] input = {"A", "B", "C", "D"}; + Arguments args = manager.parse(cmd, input); + + assertEquals("A", args.getAsString("first", null)); + assertEquals("B C D", args.get("rest")); + } + + @Test + void testNoExtraAfterInfinite() throws Exception { + // Vérifier que l'ajout d'un argument après un infini lève une exception gérée + Command cmd = new DummyCommand(); + cmd.setManager(manager); + cmd.addArgs("x:infinite"); + cmd.addArgs("y", String.class); + verify(logger).error("Arguments cannot follow infinite arguments."); + + String[] input = {"v1", "v2"}; + Arguments args = manager.parse(cmd, input); + assertEquals("v1 v2", args.get("x")); + assertNull(args.get("y")); + verify(logger).error(contains("y")); + } + + @Test + void testBasicArgParsing_correctTypes() throws Exception { + Command cmd = new DummyCommand(); + cmd.addArgs("num", Integer.class); + cmd.addOptionalArgs("opt", String.class); + + String[] input = {"42", "hello"}; + Arguments args = manager.parse(cmd, input); + + int num = args.get("num"); + assertEquals(42, num); + Optional opt = args.getOptional("opt"); + assertTrue(opt.isPresent()); + assertEquals("hello", opt.get()); + } + + @Test + void testOptionalArgs_onlyDefault() throws Exception { + Command cmd = new DummyCommand(); + cmd.addArgs("req", String.class); + cmd.addOptionalArgs("opt1", Integer.class); + cmd.addOptionalArgs("opt2", Double.class); + + String[] input = {"reqValue"}; + Arguments args = manager.parse(cmd, input); + assertEquals("reqValue", args.getAsString("req", null)); + + assertFalse(args.getOptional("opt1").isPresent()); + assertFalse(args.getOptional("opt2").isPresent()); + assertEquals(0, args.getAsInt("opt1", 0)); + assertEquals(0.0, args.getAsDouble("opt2", 0.0)); + } + + @Test + void testArgumentIncorrectException_onBadType() { + Command cmd = new DummyCommand(); + cmd.addArgs("n", Integer.class); + String[] input = {"notAnInt"}; + assertThrows(ArgumentIncorrectException.class, () -> manager.parse(cmd, input)); + } + + @Test + void testCommandRegistration_entriesInManager() { + Command cmd = new DummyCommand("main"); + cmd.addAlias("m"); + cmd.addSubCommand(new DummyCommand("sub")); + + manager.registerCommand(cmd); + Map> map = manager.getCommands(); + assertTrue(map.containsKey("main")); + assertTrue(map.containsKey("m")); + assertTrue(map.containsKey("main.sub")); + } + + @Test + void registerCommand_shouldAddMainAndAliasAndSubcommands() { + // Create command with alias and subcommand + DummyCommand main = new DummyCommand(); + main.addAlias("a1", "a2"); + DummyCommand sub = new DummyCommand(); + main.addSubCommand(sub); + + manager.registerCommand(main); + // Check platform.addCommand called for all labels + List added = platform.added; + assertTrue(added.contains("dummy")); + assertTrue(added.contains("a1")); + assertTrue(added.contains("a2")); + assertTrue(added.stream().anyMatch(label -> label.startsWith("dummy."))); + } + + @Test + void addCommand_shouldRegisterCompletersForArgs() { + // Create a command requiring two args with converters + Command cmd = new DummyCommand(); + cmd.addArgs("intArg", Integer.class); + cmd.addOptionalArgs("optArg", Double.class); + manager.registerCommand(cmd); + + Map>> comps = manager.getCompleters(); + assertTrue(comps.containsKey("dummy")); + Map> map = comps.get("dummy"); + assertTrue(map.containsKey(1)); + assertTrue(map.containsKey(2)); + } + + @Test + void addCommand_withUnknownType_shouldThrow() { + Command cmd = new DummyCommand(); + cmd.addArgs("bad:typeparser"); + assertThrows(RuntimeException.class, () -> manager.registerCommand(cmd)); + } + +} diff --git a/core/src/test/java/fr/traqueur/commands/api/CommandTest.java b/core/src/test/java/fr/traqueur/commands/api/CommandTest.java new file mode 100644 index 0000000..1e6b426 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/api/CommandTest.java @@ -0,0 +1,188 @@ +// Placez ce fichier sous core/src/test/java/fr/traqueur/commands/api/ + +package fr.traqueur.commands.api; + +import fr.traqueur.commands.api.requirements.Requirement; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.*; + +class CommandTest { + + private static class DummyCommand extends Command { + DummyCommand(String name) { + super("plugin", name); + } + DummyCommand() { + super("plugin", "dummy"); + } + + @Override + public void execute(Object sender, Arguments arguments) { + // no-op + } + } + + private DummyCommand cmd; + private CommandPlatform platform; + + @BeforeEach + void setUp() { + platform = new CommandPlatform() { + @Override public String getPlugin() { return null; } + @Override public void injectManager(CommandManager commandManager) {} + @Override public java.util.logging.Logger getLogger() { return java.util.logging.Logger.getAnonymousLogger(); } + @Override public boolean hasPermission(Object sender, String permission) { return true; } + @Override public void addCommand(Command command, String label) {} + @Override public void removeCommand(String label, boolean subcommand) {} + }; + cmd = new DummyCommand(); + } + + @Test + void testAliasesAndName() { + assertEquals("dummy", cmd.getName()); + assertTrue(cmd.getAliases().isEmpty()); + cmd.addAlias("d1", "d2"); + List aliases = cmd.getAliases(); + assertEquals(2, aliases.size()); + assertTrue(aliases.contains("d1")); + assertTrue(aliases.contains("d2")); + } + + @Test + void testSettersAndGetters() { + // Test description setter/getter + cmd.setDescription("Desc"); + assertEquals("Desc", cmd.getDescription()); + + // Test usage setter/getter + cmd.setUsage("/dummy usage"); + assertEquals("/dummy usage", cmd.getUsage()); + + // Test permission setter/getter + cmd.setPermission("perm.test"); + assertEquals("perm.test", cmd.getPermission()); + + // Test gameOnly flag + cmd.setGameOnly(true); + assertTrue(cmd.inGameOnly()); + cmd.setGameOnly(false); + assertFalse(cmd.inGameOnly()); + } + + @Test + void testAddSubCommandAndIsSubcommandFlag() { + DummyCommand sub = new DummyCommand(); + cmd.addSubCommand(sub); + List> subs = cmd.getSubcommands(); + assertEquals(1, subs.size()); + assertTrue(subs.contains(sub)); + assertTrue(sub.isSubCommand()); + } + + @Test + void testRegisterDelegatesToManager() { + AtomicBoolean called = new AtomicBoolean(false); + CommandManager fakeManager = new CommandManager(new CommandPlatform() { + @Override public String getPlugin() { return null; } + @Override public void injectManager(CommandManager commandManager) {} + @Override public java.util.logging.Logger getLogger() { return java.util.logging.Logger.getAnonymousLogger(); } + @Override public boolean hasPermission(Object sender, String permission) { return true; } + @Override public void addCommand(Command command, String label) {called.set(true);} + @Override public void removeCommand(String label, boolean subcommand) { } + }) {}; + cmd.setManager(fakeManager); + fakeManager.registerCommand(cmd); + assertTrue(called.get()); + } + + @Test + void testUnregisterDelegatesToManager() { + AtomicBoolean called = new AtomicBoolean(false); + CommandManager fakeManager = new CommandManager(new CommandPlatform() { + @Override public String getPlugin() { return null; } + @Override public void injectManager(CommandManager commandManager) {} + @Override public java.util.logging.Logger getLogger() { return java.util.logging.Logger.getAnonymousLogger(); } + @Override public boolean hasPermission(Object sender, String permission) { return true; } + @Override public void addCommand(Command command, String label) {} + @Override public void removeCommand(String label, boolean subcommand) { called.set(true); } + }) {}; + cmd.setManager(fakeManager); + cmd.unregister(); + assertTrue(called.get()); + } + + @Test + void testAddArgsAndOptionalArgs() { + // add required args + cmd.addArgs("arg1", String.class); + cmd.addArgs("arg2"); // string type + assertEquals(2, cmd.getArgs().size()); + assertEquals("arg1:string", cmd.getArgs().get(0).arg().toLowerCase()); + assertEquals("arg2:string", cmd.getArgs().get(1).arg().toLowerCase()); + + // add optional args + cmd.addOptionalArgs("opt1", Integer.class); + cmd.addOptionalArgs("opt2"); + assertEquals(2, cmd.getOptinalArgs().size()); + assertEquals("opt1:integer", cmd.getOptinalArgs().get(0).arg().toLowerCase()); + assertEquals("opt2:string", cmd.getOptinalArgs().get(1).arg().toLowerCase()); + } + + @Test + void usage_noSubs_noArgs() { + String usage = cmd.generateDefaultUsage(platform, null, "dummy"); + assertEquals("/dummy", usage); + } + + @Test + void usage_onlyRequiredArgs() { + cmd.addArgs("arg1", String.class); + cmd.addArgs("arg2", Integer.class); + String usage = cmd.generateDefaultUsage(platform, null, "dummy"); + assertTrue(usage.startsWith("/dummy ")); + } + + @Test + void usage_requiredAndOptionalArgs() { + cmd.addArgs("arg", String.class); + cmd.addOptionalArgs("opt", Double.class); + String usage = cmd.generateDefaultUsage(platform, null, "dummy"); + assertTrue(usage.contains("")); + assertTrue(usage.contains("[opt:double]")); + } + + @Test + void usage_withSubcommands() { + DummyCommand subA = new DummyCommand("suba"); + DummyCommand subB = new DummyCommand("subb"); + cmd.addSubCommand(subA, subB); + String usage = cmd.generateDefaultUsage(platform, null, "dummy"); + // extract first angle bracket content + String inside = usage.substring(usage.indexOf('<')+1, usage.indexOf('>')); + List parts = Arrays.asList(inside.split("\\|")); + assertTrue(parts.contains("suba")); + assertTrue(parts.contains("subb")); + } + + @Test + void usage_subsAndArgsCombined() { + DummyCommand subX = new DummyCommand("x"); + DummyCommand subY = new DummyCommand("y"); + cmd.addSubCommand(subX, subY); + cmd.addArgs("req", String.class); + cmd.addOptionalArgs("opt", String.class); + + String usage = cmd.generateDefaultUsage(platform, null, "dummy"); + // expect "/dummy [opt:string]" + assertTrue(usage.startsWith("/dummy ")); + assertTrue(usage.contains("")); + assertTrue(usage.contains("[opt:string]")); + } +} diff --git a/core/src/test/java/fr/traqueur/commands/api/updater/UpdaterTest.java b/core/src/test/java/fr/traqueur/commands/api/updater/UpdaterTest.java new file mode 100644 index 0000000..5b93b83 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/api/updater/UpdaterTest.java @@ -0,0 +1,95 @@ +package fr.traqueur.commands.api.updater; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.net.URL; +import java.net.URLStreamHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class UpdaterTest { + + private Logger commandsApiLogger; + private TestLogHandler logHandler; + + @BeforeEach + void setUp() { + commandsApiLogger = Logger.getLogger("CommandsAPI"); + logHandler = new TestLogHandler(); + commandsApiLogger.addHandler(logHandler); + commandsApiLogger.setLevel(Level.ALL); + Updater.setLogger(commandsApiLogger); + } + + @AfterEach + void tearDown() { + commandsApiLogger.removeHandler(logHandler); + } + + @Test + void getVersion_readsFromCommandsProperties() { + // commands.properties in test/resources contains 'version=0.0.1' + String version = Updater.getVersion(); + assertNotNull(version); + assertEquals("1.0.0", version); + } + + @Test + void testIsUpToDate_withStaticMock_equalVersions() { + try (MockedStatic mocks = mockStatic(Updater.class)) { + // stub real method to call through + mocks.when(Updater::isUpToDate).thenCallRealMethod(); + mocks.when(Updater::getVersion).thenReturn("1.0.0"); + mocks.when(Updater::fetchLatestVersion).thenReturn("1.0.0"); + + assertTrue(Updater.isUpToDate()); + mocks.verify(Updater::getVersion); + mocks.verify(Updater::fetchLatestVersion); + } + } + + @Test + void testIsUpToDate_withStaticMock_differentVersions() { + try (MockedStatic mocks = mockStatic(Updater.class)) { + mocks.when(Updater::isUpToDate).thenCallRealMethod(); + mocks.when(Updater::getVersion).thenReturn("1.0.0"); + mocks.when(Updater::fetchLatestVersion).thenReturn("2.0.0"); + + assertFalse(Updater.isUpToDate()); + } + } + + @Test + void testCheckUpdates_logsWarningWhenNotUpToDate() { + try (MockedStatic mocks = mockStatic(Updater.class)) { + mocks.when(Updater::checkUpdates).thenCallRealMethod(); + mocks.when(Updater::getVersion).thenReturn("1.0.0"); + mocks.when(Updater::fetchLatestVersion).thenReturn("2.0.0"); + + Updater.checkUpdates(); + assertTrue(logHandler.anyMatch(Level.WARNING, + rec -> rec.getMessage().contains("latest version is 2.0.0") + )); + } + } + + /** Captures log records for assertions */ + private static class TestLogHandler extends Handler { + private final java.util.List records = new java.util.ArrayList<>(); + @Override public void publish(LogRecord record) { records.add(record); } + @Override public void flush() {} + @Override public void close() {} + boolean anyMatch(Level lvl, java.util.function.Predicate p) { + return records.stream() + .anyMatch(r -> r.getLevel().equals(lvl) && p.test(r)); + } + } +} diff --git a/core/src/test/java/fr/traqueur/commands/impl/arguments/BooleanArgumentTest.java b/core/src/test/java/fr/traqueur/commands/impl/arguments/BooleanArgumentTest.java new file mode 100644 index 0000000..2b11529 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/impl/arguments/BooleanArgumentTest.java @@ -0,0 +1,44 @@ +package fr.traqueur.commands.impl.arguments; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class BooleanArgumentTest { + + private BooleanArgument converter; + + @BeforeEach + void setUp() { + converter = new BooleanArgument<>(); + } + + @Test + void testApply_validTrueFalse() { + assertTrue(converter.apply("true")); + assertFalse(converter.apply("false")); + assertTrue(converter.apply("TRUE")); + assertFalse(converter.apply("FaLsE")); + } + + @Test + void testApply_invalid() { + assertNull(converter.apply("yes")); + assertNull(converter.apply("0")); + assertNull(converter.apply("")); + assertNull(converter.apply(null)); + } + + @Test + void testOnCompletion() { + List completions = converter.onCompletion(null, Collections.emptyList()); + assertEquals(2, completions.size()); + assertTrue(completions.contains("true")); + assertTrue(completions.contains("false")); + } +} \ No newline at end of file diff --git a/core/src/test/java/fr/traqueur/commands/impl/arguments/DoubleArgumentTest.java b/core/src/test/java/fr/traqueur/commands/impl/arguments/DoubleArgumentTest.java new file mode 100644 index 0000000..b11b3fc --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/impl/arguments/DoubleArgumentTest.java @@ -0,0 +1,28 @@ +package fr.traqueur.commands.impl.arguments; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class DoubleArgumentTest { + private DoubleArgument converter; + + @BeforeEach + void setUp() { + converter = new DoubleArgument(); + } + + @Test + void testApply_valid() { + assertEquals(3.14, converter.apply("3.14")); + assertEquals(-2.718, converter.apply("-2.718")); + assertEquals(0.0, converter.apply("0")); + } + + @Test + void testApply_invalid() { + assertNull(converter.apply("abc")); + assertNull(converter.apply(null)); + } +} \ No newline at end of file diff --git a/core/src/test/java/fr/traqueur/commands/impl/arguments/EnumArgumentTest.java b/core/src/test/java/fr/traqueur/commands/impl/arguments/EnumArgumentTest.java new file mode 100644 index 0000000..d497941 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/impl/arguments/EnumArgumentTest.java @@ -0,0 +1,43 @@ +package fr.traqueur.commands.impl.arguments; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class EnumArgumentTest { + + private enum Sample {ONE, TWO, THREE} + private EnumArgument converter; + + @BeforeEach + void setUp() { + converter = EnumArgument.of(Sample.class); + } + + @Test + void testApply_valid() { + assertEquals(Sample.ONE, converter.apply("ONE")); + assertEquals(Sample.TWO, converter.apply("TWO")); + } + + @Test + void testApply_invalid() { + assertNull(converter.apply("one")); // case-sensitive + assertNull(converter.apply("FOUR")); + assertNull(converter.apply("")); + assertNull(converter.apply(null)); + } + + @Test + void testOnCompletion() { + List completions = converter.onCompletion(null, Collections.emptyList()); + assertEquals(3, completions.size()); + assertTrue(completions.contains("ONE")); + assertTrue(completions.contains("TWO")); + assertTrue(completions.contains("THREE")); + } +} diff --git a/core/src/test/java/fr/traqueur/commands/impl/arguments/IntegerArgumentTest.java b/core/src/test/java/fr/traqueur/commands/impl/arguments/IntegerArgumentTest.java new file mode 100644 index 0000000..be57a99 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/impl/arguments/IntegerArgumentTest.java @@ -0,0 +1,31 @@ +package fr.traqueur.commands.impl.arguments; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class IntegerArgumentTest { + + private IntegerArgument converter; + + @BeforeEach + void setUp() { + converter = new IntegerArgument(); + } + + @Test + void testApply_valid() { + assertEquals(0, converter.apply("0")); + assertEquals(123, converter.apply("123")); + assertEquals(-42, converter.apply("-42")); + } + + @Test + void testApply_invalid() { + assertNull(converter.apply("abc")); + assertNull(converter.apply("12.3")); + assertNull(converter.apply("")); + assertNull(converter.apply(null)); + } +} \ No newline at end of file diff --git a/core/src/test/java/fr/traqueur/commands/impl/arguments/LongArgumentTest.java b/core/src/test/java/fr/traqueur/commands/impl/arguments/LongArgumentTest.java new file mode 100644 index 0000000..ab632b1 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/impl/arguments/LongArgumentTest.java @@ -0,0 +1,31 @@ +package fr.traqueur.commands.impl.arguments; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class LongArgumentTest { + + private LongArgument converter; + + @BeforeEach + void setUp() { + converter = new LongArgument(); + } + + @Test + void testApply_valid() { + assertEquals(0L, converter.apply("0")); + assertEquals(1234567890123L, converter.apply("1234567890123")); + assertEquals(-9876543210L, converter.apply("-9876543210")); + } + + @Test + void testApply_invalid() { + assertNull(converter.apply("abc")); + assertNull(converter.apply("12.34")); + assertNull(converter.apply("")); + assertNull(converter.apply(null)); + } +} \ No newline at end of file diff --git a/core/src/test/java/fr/traqueur/commands/impl/logging/InternalMessageHandlerTest.java b/core/src/test/java/fr/traqueur/commands/impl/logging/InternalMessageHandlerTest.java new file mode 100644 index 0000000..77f5f1c --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/impl/logging/InternalMessageHandlerTest.java @@ -0,0 +1,43 @@ +// src/test/java/fr/traqueur/commands/api/logging/InternalMessageHandlerTest.java +package fr.traqueur.commands.api.logging; + +import fr.traqueur.commands.impl.logging.InternalMessageHandler; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class InternalMessageHandlerTest { + + private final MessageHandler handler = new InternalMessageHandler(); + + @Test + void testGetNoPermissionMessage() { + assertEquals( + "&cYou do not have permission to use this command.", + handler.getNoPermissionMessage() + ); + } + + @Test + void testGetOnlyInGameMessage() { + assertEquals( + "&cYou can only use this command in-game.", + handler.getOnlyInGameMessage() + ); + } + + @Test + void testGetArgNotRecognized() { + assertEquals( + "&cArgument &e%arg% &cnot recognized.", + handler.getArgNotRecognized() + ); + } + + @Test + void testGetRequirementMessage() { + assertEquals( + "The requirement %requirement% was not met", + handler.getRequirementMessage() + ); + } +} diff --git a/core/src/test/resources/commands.properties b/core/src/test/resources/commands.properties new file mode 100644 index 0000000..beb72cc --- /dev/null +++ b/core/src/test/resources/commands.properties @@ -0,0 +1 @@ +version=1.0.0 \ No newline at end of file diff --git a/spigot/build.gradle b/spigot/build.gradle index 159ec37..97c715e 100644 --- a/spigot/build.gradle +++ b/spigot/build.gradle @@ -16,6 +16,7 @@ repositories { dependencies { implementation project(":core") compileOnly "org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT" + testImplementation ("org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT") } java { diff --git a/spigot/src/main/java/fr/traqueur/commands/spigot/requirements/ZoneRequirement.java b/spigot/src/main/java/fr/traqueur/commands/spigot/requirements/ZoneRequirement.java index fd11236..c834539 100644 --- a/spigot/src/main/java/fr/traqueur/commands/spigot/requirements/ZoneRequirement.java +++ b/spigot/src/main/java/fr/traqueur/commands/spigot/requirements/ZoneRequirement.java @@ -22,7 +22,7 @@ public class ZoneRequirement implements Requirement { * @param locationDown The second location of the zone. * @return The zone requirement. */ - public static Requirement of(Location locationUp, Location locationDown) { + public static Requirement of(Location locationUp, Location locationDown) { return new ZoneRequirement(locationUp, locationDown); } @@ -37,7 +37,7 @@ public static Requirement of(Location locationUp, Location locationDown) { * @param z2 The second z coordinate of the zone. * @return The zone requirement. */ - public static Requirement of(World world, int x1, int y1, int z1, int x2, int y2, int z2) { + public static Requirement of(World world, int x1, int y1, int z1, int x2, int y2, int z2) { return new ZoneRequirement(new Location(world, x1, y1, z1), new Location(world, x2, y2, z2)); } @@ -51,7 +51,7 @@ public static Requirement of(World world, int x1, int y1, int z1, int x2, int y2 * @param z2 The second z coordinate of the zone. * @return The zone requirement. */ - public static Requirement of(World world, int x1, int z1, int x2, int z2) { + public static Requirement of(World world, int x1, int z1, int x2, int z2) { return new ZoneRequirement(new Location(world, x1, world.getMaxHeight(), z1), new Location(world, x2, world.getMinHeight(), z2)); } @@ -67,7 +67,8 @@ public ZoneRequirement(Location locationUp, Location locationDown) { if(locationUp.getWorld() == null || locationDown.getWorld() == null) { throw new IllegalArgumentException("The locations must not be null."); } - if(locationUp.getWorld().getName().equals(locationDown.getWorld().getName())) { + + if(!locationUp.getWorld().getName().equals(locationDown.getWorld().getName())) { throw new IllegalArgumentException("The locations must be in the same world."); } diff --git a/spigot/src/test/java/fr/traqueur/commands/spigot/SpigotIntegrationTest.java b/spigot/src/test/java/fr/traqueur/commands/spigot/SpigotIntegrationTest.java new file mode 100644 index 0000000..2006a49 --- /dev/null +++ b/spigot/src/test/java/fr/traqueur/commands/spigot/SpigotIntegrationTest.java @@ -0,0 +1,65 @@ +package fr.traqueur.commands.spigot; + +import fr.traqueur.commands.api.Arguments; +import fr.traqueur.commands.api.Command; +import fr.traqueur.commands.api.CommandPlatform; +import fr.traqueur.commands.spigot.arguments.PlayerArgument; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.*; + +class SpigotIntegrationTest { + + private fr.traqueur.commands.api.CommandManager manager; + private MockedStatic bukkitStatic; + + @BeforeEach + void setUp() { + manager = new fr.traqueur.commands.api.CommandManager(new CommandPlatform() { + @Override public Object getPlugin() { return null; } + @Override public void injectManager(fr.traqueur.commands.api.CommandManager cm) {} + @Override public java.util.logging.Logger getLogger() { return java.util.logging.Logger.getAnonymousLogger(); } + @Override public boolean hasPermission(CommandSender sender, String permission) { return true; } + @Override public void addCommand(fr.traqueur.commands.api.Command command, String label) {} + @Override public void removeCommand(String label, boolean subcommand) {} + }) {}; + manager.registerConverter(Player.class, new PlayerArgument()); + bukkitStatic = Mockito.mockStatic(Bukkit.class); + } + + @AfterEach + void tearDown() { + bukkitStatic.close(); + } + + @Test + void testFullPipeline_playerArgument() throws Exception { + Command cmd = new Command(null, "test") { + @Override + public void execute(CommandSender sender, Arguments arguments) { + Player p = arguments.get("playerName"); + assertNotNull(p); + assertEquals("User1", p.getName()); + } + }; + cmd.addArgs("playerName", Player.class); + manager.registerCommand(cmd); + + Player mockPlayer = Mockito.mock(Player.class); + Mockito.when(mockPlayer.getName()).thenReturn("User1"); + bukkitStatic.when(() -> Bukkit.getPlayer("User1")).thenReturn(mockPlayer); + + String[] argsArr = new String[]{"User1"}; + Arguments parsed = manager.parse(cmd, argsArr); + Player p = parsed.get("playerName"); + assertSame(mockPlayer, p); + cmd.execute(null, parsed); + } +} diff --git a/spigot/src/test/java/fr/traqueur/commands/spigot/arguments/OfflinePlayerArgumentTest.java b/spigot/src/test/java/fr/traqueur/commands/spigot/arguments/OfflinePlayerArgumentTest.java new file mode 100644 index 0000000..ad6e126 --- /dev/null +++ b/spigot/src/test/java/fr/traqueur/commands/spigot/arguments/OfflinePlayerArgumentTest.java @@ -0,0 +1,56 @@ +package fr.traqueur.commands.spigot.arguments; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class OfflinePlayerArgumentTest { + + @Test + void testApply_returnsOfflinePlayerWhenFound() { + OfflinePlayer mockOff = Mockito.mock(OfflinePlayer.class); + try (MockedStatic mocked = Mockito.mockStatic(Bukkit.class)) { + mocked.when(() -> Bukkit.getOfflinePlayer("Charlie")).thenReturn(mockOff); + + OfflinePlayerArgument converter = new OfflinePlayerArgument(); + assertSame(mockOff, converter.apply("Charlie")); + } + } + + @Test + void testApply_returnsNullOnNullInput() { + OfflinePlayerArgument converter = new OfflinePlayerArgument(); + assertNull(converter.apply(null)); + } + + @Test + void testOnCompletion_listsAllOfflinePlayerNames() { + OfflinePlayer o1 = Mockito.mock(OfflinePlayer.class); + OfflinePlayer o2 = Mockito.mock(OfflinePlayer.class); + Mockito.when(o1.getName()).thenReturn("Dave"); + Mockito.when(o2.getName()).thenReturn("Eve"); + OfflinePlayer[] offs = new OfflinePlayer[]{o1, o2}; + + org.bukkit.Server mockServer = Mockito.mock(org.bukkit.Server.class); + Mockito.when(mockServer.getOfflinePlayers()).thenReturn(offs); + + try (MockedStatic mocked = Mockito.mockStatic(Bukkit.class)) { + mocked.when(Bukkit::getServer).thenReturn(mockServer); + + OfflinePlayerArgument converter = new OfflinePlayerArgument(); + List completions = converter.onCompletion(Mockito.mock(CommandSender.class), Collections.emptyList()); + assertEquals(2, completions.size()); + assertTrue(completions.contains("Dave")); + assertTrue(completions.contains("Eve")); + } + } +} diff --git a/spigot/src/test/java/fr/traqueur/commands/spigot/arguments/PlayerArgumentTest.java b/spigot/src/test/java/fr/traqueur/commands/spigot/arguments/PlayerArgumentTest.java new file mode 100644 index 0000000..2249510 --- /dev/null +++ b/spigot/src/test/java/fr/traqueur/commands/spigot/arguments/PlayerArgumentTest.java @@ -0,0 +1,58 @@ +package fr.traqueur.commands.spigot.arguments; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class PlayerArgumentTest { + + @Test + void testApply_returnsPlayerWhenFound() { + Player mockPlayer = Mockito.mock(Player.class); + // Stub static Bukkit.getPlayer + try (MockedStatic mocked = Mockito.mockStatic(Bukkit.class)) { + mocked.when(() -> Bukkit.getPlayer("Alice")).thenReturn(mockPlayer); + + PlayerArgument converter = new PlayerArgument(); + assertSame(mockPlayer, converter.apply("Alice")); + } + } + + @Test + void testApply_returnsNullWhenNotFoundOrNullInput() { + PlayerArgument converter = new PlayerArgument(); + // null input + assertNull(converter.apply(null)); + // stub to return null for unknown name + try (MockedStatic mocked = Mockito.mockStatic(Bukkit.class)) { + mocked.when(() -> Bukkit.getPlayer("Bob")).thenReturn(null); + assertNull(converter.apply("Bob")); + } + } + + @Test + void testOnCompletion_listsOnlinePlayerNames() { + Player p1 = Mockito.mock(Player.class); + Player p2 = Mockito.mock(Player.class); + Mockito.when(p1.getName()).thenReturn("Alice"); + Mockito.when(p2.getName()).thenReturn("Bob"); + Set online = new HashSet<>(Arrays.asList(p1, p2)); + + try (MockedStatic mocked = Mockito.mockStatic(Bukkit.class)) { + // Stub Bukkit.getOnlinePlayers to return our set + mocked.when(Bukkit::getOnlinePlayers).thenReturn(online); + + PlayerArgument converter = new PlayerArgument(); + List completions = converter.onCompletion(null, new ArrayList<>()); + assertEquals(2, completions.size()); + assertTrue(completions.contains("Alice")); + assertTrue(completions.contains("Bob")); + } + } +} \ No newline at end of file diff --git a/spigot/src/test/java/fr/traqueur/commands/spigot/requirements/WorldRequirementTest.java b/spigot/src/test/java/fr/traqueur/commands/spigot/requirements/WorldRequirementTest.java new file mode 100644 index 0000000..5241709 --- /dev/null +++ b/spigot/src/test/java/fr/traqueur/commands/spigot/requirements/WorldRequirementTest.java @@ -0,0 +1,50 @@ +package fr.traqueur.commands.spigot.requirements; + +import fr.traqueur.commands.api.requirements.Requirement; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class WorldRequirementTest { + private World mockWorld; + private Player mockPlayer; + private CommandSender mockSender; + private Requirement requirement; + + @BeforeEach + void setUp() { + mockWorld = Mockito.mock(World.class); + when(mockWorld.getName()).thenReturn("worldA"); + when(mockWorld.getUID()).thenReturn(java.util.UUID.randomUUID()); + + mockPlayer = Mockito.mock(Player.class); + when(mockPlayer.getWorld()).thenReturn(mockWorld); + + mockSender = Mockito.mock(CommandSender.class); + } + + @Test + void testCheck_playerInCorrectWorld() { + requirement = new WorldRequirement(mockWorld); + assertTrue(requirement.check(mockPlayer)); + } + + @Test + void testCheck_senderNotPlayer() { + requirement = new WorldRequirement(mockWorld); + assertFalse(requirement.check(mockSender)); + } + + @Test + void testErrorMessage_containsWorldName() { + requirement = new WorldRequirement(mockWorld); + String msg = requirement.errorMessage(); + assertTrue(msg.contains("worldA")); + } +} diff --git a/spigot/src/test/java/fr/traqueur/commands/spigot/requirements/ZoneRequirementTest.java b/spigot/src/test/java/fr/traqueur/commands/spigot/requirements/ZoneRequirementTest.java new file mode 100644 index 0000000..848f179 --- /dev/null +++ b/spigot/src/test/java/fr/traqueur/commands/spigot/requirements/ZoneRequirementTest.java @@ -0,0 +1,74 @@ +package fr.traqueur.commands.spigot.requirements; + +import fr.traqueur.commands.api.requirements.Requirement; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class ZoneRequirementTest { + + @Test + void testConstructor_sameWorld_throwsException() { + World world = Mockito.mock(World.class); + when(world.getName()).thenReturn("same"); + Location loc1 = new Location(world, 0, 0, 0); + Location loc2 = new Location(world, 1, 1, 1); + Requirement req = new ZoneRequirement(loc1, loc2); + assertNotNull(req); + } + + @Test + void testConstructor_differentWorld_createsRequirement() { + World world1 = Mockito.mock(World.class); + when(world1.getName()).thenReturn("w1"); + World world2 = Mockito.mock(World.class); + when(world2.getName()).thenReturn("w2"); + Location up = new Location(world1, 10, 10, 10); + Location down = new Location(world2, -5, -5, -5); + assertThrows(IllegalArgumentException.class, + () -> new ZoneRequirement(up, down)); + } + + @Test + void testCheck_withinZone() { + World world1 = Mockito.mock(World.class); + when(world1.getName()).thenReturn("w1"); + Requirement req = ZoneRequirement.of(world1, 0, 0, 0, 5, 5, 5); + + Player player = Mockito.mock(Player.class); + when(player.getWorld()).thenReturn(world1); + Location playerLoc = new Location(world1, 3, 3, 3); + when(player.getLocation()).thenReturn(playerLoc); + + assertTrue(req.check(player)); + } + + @Test + void testCheck_outsideZone() { + World world1 = Mockito.mock(World.class); + when(world1.getName()).thenReturn("w1"); + Requirement req = ZoneRequirement.of(world1, 0, 0, 0, 5, 5, 5); + + Player player = Mockito.mock(Player.class); + when(player.getWorld()).thenReturn(world1); + Location outside = new Location(world1, 10, 10, 10); + when(player.getLocation()).thenReturn(outside); + assertFalse(req.check(player)); + } + + @Test + void testErrorMessage_containsCoords() { + World world1 = Mockito.mock(World.class); + when(world1.getName()).thenReturn("w1"); + Requirement req = ZoneRequirement.of(world1, 0, 0, 0, 5, 5, 5); + String msg = req.errorMessage(); + assertTrue(msg.contains("0")); + assertTrue(msg.contains("5")); + } +} \ No newline at end of file