From 8f1dce20e9e776f7e2f5f4a9a292a3bc8e185df3 Mon Sep 17 00:00:00 2001 From: Traqueur_ Date: Mon, 7 Jul 2025 14:57:33 +0200 Subject: [PATCH 1/5] feat: add firt part of tests --- build.gradle | 46 ++++- core/build.gradle | 2 +- .../fr/traqueur/commands/api/Command.java | 4 + .../ArgsWithInfiniteArgumentException.java | 2 +- .../impl/arguments/BooleanArgument.java | 3 + .../impl/arguments/DoubleArgument.java | 3 + .../commands/impl/arguments/EnumArgument.java | 3 + .../impl/arguments/IntegerArgument.java | 3 + .../commands/impl/arguments/LongArgument.java | 3 + .../traqueur/commands/api/ArgumentsTest.java | 98 +++++++++++ .../commands/api/CommandManagerTest.java | 154 +++++++++++++++++ .../fr/traqueur/commands/api/CommandTest.java | 159 ++++++++++++++++++ .../impl/arguments/BooleanArgumentTest.java | 44 +++++ .../impl/arguments/DoubleArgumentTest.java | 28 +++ .../impl/arguments/EnumArgumentTest.java | 43 +++++ .../impl/arguments/IntegerArgumentTest.java | 31 ++++ .../impl/arguments/LongArgumentTest.java | 31 ++++ spigot/build.gradle | 1 + .../spigot/requirements/ZoneRequirement.java | 9 +- .../spigot/SpigotIntegrationTest.java | 65 +++++++ .../arguments/OfflinePlayerArgumentTest.java | 56 ++++++ .../spigot/arguments/PlayerArgumentTest.java | 58 +++++++ .../requirements/WorldRequirementTest.java | 50 ++++++ .../requirements/ZoneRequirementTest.java | 74 ++++++++ 24 files changed, 963 insertions(+), 7 deletions(-) create mode 100644 core/src/test/java/fr/traqueur/commands/api/ArgumentsTest.java create mode 100644 core/src/test/java/fr/traqueur/commands/api/CommandManagerTest.java create mode 100644 core/src/test/java/fr/traqueur/commands/api/CommandTest.java create mode 100644 core/src/test/java/fr/traqueur/commands/impl/arguments/BooleanArgumentTest.java create mode 100644 core/src/test/java/fr/traqueur/commands/impl/arguments/DoubleArgumentTest.java create mode 100644 core/src/test/java/fr/traqueur/commands/impl/arguments/EnumArgumentTest.java create mode 100644 core/src/test/java/fr/traqueur/commands/impl/arguments/IntegerArgumentTest.java create mode 100644 core/src/test/java/fr/traqueur/commands/impl/arguments/LongArgumentTest.java create mode 100644 spigot/src/test/java/fr/traqueur/commands/spigot/SpigotIntegrationTest.java create mode 100644 spigot/src/test/java/fr/traqueur/commands/spigot/arguments/OfflinePlayerArgumentTest.java create mode 100644 spigot/src/test/java/fr/traqueur/commands/spigot/arguments/PlayerArgumentTest.java create mode 100644 spigot/src/test/java/fr/traqueur/commands/spigot/requirements/WorldRequirementTest.java create mode 100644 spigot/src/test/java/fr/traqueur/commands/spigot/requirements/ZoneRequirementTest.java diff --git a/build.gradle b/build.gradle index 30890b7..a3f3f10 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,50 @@ 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'] + } + + testlogger { + theme 'mocha-parallel' + logLevel 'quiet' + showPassed true + showSkipped true + showFailed true + showSummary true + showStandardStreams true + } + } +} + +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/Command.java b/core/src/main/java/fr/traqueur/commands/api/Command.java index 85fe236..929650d 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() 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/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..e2bfdbd --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/api/ArgumentsTest.java @@ -0,0 +1,98 @@ +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.contains; +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); + } +} \ 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..4c5d864 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/api/CommandManagerTest.java @@ -0,0 +1,154 @@ +package fr.traqueur.commands.api; + +import fr.traqueur.commands.api.exceptions.ArgumentIncorrectException; +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.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 static class FakePlatform implements CommandPlatform { + @Override public Object getPlugin() { return null; } + @Override public void injectManager(CommandManager commandManager) {} + @Override public Logger getLogger() { return Logger.getAnonymousLogger(); } + @Override public boolean hasPermission(String sender, String permission) { return true; } + @Override public void addCommand(Command command, String label) {} + @Override public void removeCommand(String label, boolean subcommand) {} + } + + @BeforeEach + void setUp() { + FakePlatform 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 Command(null, "cmd") { + @Override public void execute(String sender, Arguments arguments) {} + }; + 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 Command(null, "err") { + @Override public void execute(String sender, Arguments arguments) {} + }; + 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 Command(null, "basic") { + @Override public void execute(String sender, Arguments arguments) {} + }; + 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 Command(null, "opt") { + @Override public void execute(String sender, Arguments arguments) {} + }; + 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 Command(null, "badtype") { + @Override public void execute(String sender, Arguments arguments) {} + }; + cmd.addArgs("n", Integer.class); + String[] input = {"notAnInt"}; + assertThrows(ArgumentIncorrectException.class, () -> manager.parse(cmd, input)); + } + + @Test + void testCommandRegistration_entriesInManager() { + Command cmd = new Command(null, "main") { + @Override public void execute(String sender, Arguments arguments) {} + }; + cmd.addAlias("m"); + cmd.addSubCommand(new Command(null, "sub") { + @Override public void execute(String sender, Arguments arguments) {} + }); + + manager.registerCommand(cmd); + Map> map = manager.getCommands(); + assertTrue(map.containsKey("main")); + assertTrue(map.containsKey("m")); + assertTrue(map.containsKey("main.sub")); + } + +} 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..0259c92 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/api/CommandTest.java @@ -0,0 +1,159 @@ +// 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.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +class CommandTest { + + private static class DummyCommand extends Command { + DummyCommand() { + super("plugin", "dummy"); + } + + @Override + public void execute(Object sender, Arguments arguments) { + // no-op + } + } + + private DummyCommand cmd; + + @BeforeEach + void setUp() { + 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 testGenerateDefaultUsage_noSubs_noArgs() { + // when no subcommands or args, just /dummy + String usage = cmd.generateDefaultUsage(null, null, "dummy"); + assertEquals("/dummy ", usage); + } + + @Test + void testGenerateDefaultUsage_withSubsAndArgs() { + // prepare subcommands + DummyCommand subA = new DummyCommand(); + subA.setUsage("/dummy suba"); + DummyCommand subB = new DummyCommand(); + cmd.addSubCommand(subA, subB); + // prepare args + cmd.addArgs("x", Integer.class); + cmd.addOptionalArgs("y", String.class); + + // simulate platform and sender stub + CommandPlatform 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) {} + }; + String gen = cmd.generateDefaultUsage(platform, null, "dummy"); + // expected: /dummy [y] + assertTrue(gen.startsWith("/dummy <")); + assertTrue(gen.contains("")); + assertTrue(gen.contains("[y:string]")); + } +} 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/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 From 9a809e24d7a558dc0496bc0252a989ecc5fbc052 Mon Sep 17 00:00:00 2001 From: Traqueur_ Date: Mon, 7 Jul 2025 16:08:44 +0200 Subject: [PATCH 2/5] feat: add missing tests --- .../fr/traqueur/commands/api/Arguments.java | 1 + .../fr/traqueur/commands/api/Command.java | 9 +- .../commands/api/updater/Updater.java | 32 +++++-- .../traqueur/commands/api/ArgumentsTest.java | 21 ++++ .../commands/api/CommandManagerTest.java | 92 ++++++++++++------ .../fr/traqueur/commands/api/CommandTest.java | 83 ++++++++++------ .../commands/api/updater/UpdaterTest.java | 95 +++++++++++++++++++ .../logging/InternalMessageHandlerTest.java | 43 +++++++++ core/src/test/resources/commands.properties | 1 + 9 files changed, 314 insertions(+), 63 deletions(-) create mode 100644 core/src/test/java/fr/traqueur/commands/api/updater/UpdaterTest.java create mode 100644 core/src/test/java/fr/traqueur/commands/impl/logging/InternalMessageHandlerTest.java create mode 100644 core/src/test/resources/commands.properties 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 929650d..f8739db 100644 --- a/core/src/main/java/fr/traqueur/commands/api/Command.java +++ b/core/src/main/java/fr/traqueur/commands/api/Command.java @@ -498,7 +498,7 @@ public String generateDefaultUsage(CommandPlatform platform, S sender, Stri usage.append("/"); Arrays.stream(label.split("\\.")).forEach(s -> usage.append(s).append(" ")); //remove the last space - if(this.args.isEmpty() || this.optionalArgs.isEmpty()) { + if(this.args.isEmpty() && this.optionalArgs.isEmpty()) { usage.deleteCharAt(usage.length() - 1); } @@ -515,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/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/test/java/fr/traqueur/commands/api/ArgumentsTest.java b/core/src/test/java/fr/traqueur/commands/api/ArgumentsTest.java index e2bfdbd..3798593 100644 --- a/core/src/test/java/fr/traqueur/commands/api/ArgumentsTest.java +++ b/core/src/test/java/fr/traqueur/commands/api/ArgumentsTest.java @@ -7,7 +7,9 @@ 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 { @@ -95,4 +97,23 @@ void testInfiniteArgsBehavior() { 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 index 4c5d864..f0eb688 100644 --- a/core/src/test/java/fr/traqueur/commands/api/CommandManagerTest.java +++ b/core/src/test/java/fr/traqueur/commands/api/CommandManagerTest.java @@ -1,11 +1,15 @@ 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; @@ -18,18 +22,27 @@ class CommandManagerTest { private InternalLogger logger; private CommandManager manager; - private static class FakePlatform implements CommandPlatform { + 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 commandManager) {} - @Override public Logger getLogger() { return Logger.getAnonymousLogger(); } + @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) {} - @Override public void removeCommand(String label, boolean subcommand) {} + @Override public void addCommand(Command command, String label) { added.add(label); } + @Override public void removeCommand(String label, boolean sub) {} } @BeforeEach void setUp() { - FakePlatform platform = new FakePlatform(); + platform = new FakePlatform(); manager = new CommandManager(platform) {}; platform.injectManager(manager); logger = Mockito.mock(InternalLogger.class); @@ -55,9 +68,7 @@ public void execute(String sender, Arguments arguments) {} @Test void testInfiniteArgsStopsFurtherParsing() throws Exception { - Command cmd = new Command(null, "cmd") { - @Override public void execute(String sender, Arguments arguments) {} - }; + Command cmd = new DummyCommand(); cmd.setManager(manager); cmd.addArgs("first", String.class); cmd.addArgs("rest:infinite"); @@ -72,9 +83,7 @@ void testInfiniteArgsStopsFurtherParsing() throws Exception { @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 Command(null, "err") { - @Override public void execute(String sender, Arguments arguments) {} - }; + Command cmd = new DummyCommand(); cmd.setManager(manager); cmd.addArgs("x:infinite"); cmd.addArgs("y", String.class); @@ -89,9 +98,7 @@ void testNoExtraAfterInfinite() throws Exception { @Test void testBasicArgParsing_correctTypes() throws Exception { - Command cmd = new Command(null, "basic") { - @Override public void execute(String sender, Arguments arguments) {} - }; + Command cmd = new DummyCommand(); cmd.addArgs("num", Integer.class); cmd.addOptionalArgs("opt", String.class); @@ -107,9 +114,7 @@ void testBasicArgParsing_correctTypes() throws Exception { @Test void testOptionalArgs_onlyDefault() throws Exception { - Command cmd = new Command(null, "opt") { - @Override public void execute(String sender, Arguments arguments) {} - }; + Command cmd = new DummyCommand(); cmd.addArgs("req", String.class); cmd.addOptionalArgs("opt1", Integer.class); cmd.addOptionalArgs("opt2", Double.class); @@ -126,9 +131,7 @@ void testOptionalArgs_onlyDefault() throws Exception { @Test void testArgumentIncorrectException_onBadType() { - Command cmd = new Command(null, "badtype") { - @Override public void execute(String sender, Arguments arguments) {} - }; + Command cmd = new DummyCommand(); cmd.addArgs("n", Integer.class); String[] input = {"notAnInt"}; assertThrows(ArgumentIncorrectException.class, () -> manager.parse(cmd, input)); @@ -136,13 +139,9 @@ void testArgumentIncorrectException_onBadType() { @Test void testCommandRegistration_entriesInManager() { - Command cmd = new Command(null, "main") { - @Override public void execute(String sender, Arguments arguments) {} - }; + Command cmd = new DummyCommand("main"); cmd.addAlias("m"); - cmd.addSubCommand(new Command(null, "sub") { - @Override public void execute(String sender, Arguments arguments) {} - }); + cmd.addSubCommand(new DummyCommand("sub")); manager.registerCommand(cmd); Map> map = manager.getCommands(); @@ -151,4 +150,43 @@ void testCommandRegistration_entriesInManager() { 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 index 0259c92..1e6b426 100644 --- a/core/src/test/java/fr/traqueur/commands/api/CommandTest.java +++ b/core/src/test/java/fr/traqueur/commands/api/CommandTest.java @@ -6,15 +6,18 @@ 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 java.util.stream.Collectors; 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"); } @@ -26,9 +29,18 @@ public void execute(Object sender, Arguments arguments) { } 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(); } @@ -124,36 +136,53 @@ void testAddArgsAndOptionalArgs() { } @Test - void testGenerateDefaultUsage_noSubs_noArgs() { - // when no subcommands or args, just /dummy - String usage = cmd.generateDefaultUsage(null, null, "dummy"); - assertEquals("/dummy ", usage); + 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 testGenerateDefaultUsage_withSubsAndArgs() { - // prepare subcommands - DummyCommand subA = new DummyCommand(); - subA.setUsage("/dummy suba"); - DummyCommand subB = new DummyCommand(); + void usage_withSubcommands() { + DummyCommand subA = new DummyCommand("suba"); + DummyCommand subB = new DummyCommand("subb"); cmd.addSubCommand(subA, subB); - // prepare args - cmd.addArgs("x", Integer.class); - cmd.addOptionalArgs("y", String.class); + 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")); + } - // simulate platform and sender stub - CommandPlatform 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) {} - }; - String gen = cmd.generateDefaultUsage(platform, null, "dummy"); - // expected: /dummy [y] - assertTrue(gen.startsWith("/dummy <")); - assertTrue(gen.contains("")); - assertTrue(gen.contains("[y:string]")); + @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/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 From fbdf8a6d2b19dd479221793eab2a1eb8ed676894 Mon Sep 17 00:00:00 2001 From: Traqueur_ Date: Mon, 7 Jul 2025 16:17:34 +0200 Subject: [PATCH 3/5] feat(github): add workflow --- .github/workflows/test-all.yml | 49 ++++++++++++++++++++++++++++++++++ build.gradle | 19 +++++++------ 2 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/test-all.yml diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml new file mode 100644 index 0000000..077193e --- /dev/null +++ b/.github/workflows/test-all.yml @@ -0,0 +1,49 @@ +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: 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 with Gradle + run: ./gradlew clean testAll --no-daemon + + - name: Upload test reports + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-results + path: | + **/build/test-results/**/*.xml + **/build/reports/tests/** + + - name: Publish coverage to GitHub (optional) + if: success() + uses: codecov/codecov-action@v3 + with: + files: build/reports/jacoco/test/jacocoTestReport.xml \ No newline at end of file diff --git a/build.gradle b/build.gradle index a3f3f10..ed4bb89 100644 --- a/build.gradle +++ b/build.gradle @@ -34,16 +34,15 @@ subprojects { test { useJUnitPlatform() jvmArgs += ['-XX:+EnableDynamicAgentLoading'] - } - - testlogger { - theme 'mocha-parallel' - logLevel 'quiet' - showPassed true - showSkipped true - showFailed true - showSummary true - showStandardStreams true + reports { + html.required.set(true) + junitXml.required.set(true) + } + + testLogging { + events("passed", "skipped", "failed") + exceptionFormat "full" + } } } } From 5e9eef59de3f7568ae307a488a3be80a7403d0d0 Mon Sep 17 00:00:00 2001 From: Traqueur_ Date: Mon, 7 Jul 2025 16:18:54 +0200 Subject: [PATCH 4/5] fix(worlflow): version of upload artifact --- .github/workflows/test-all.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 077193e..018ac79 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -30,20 +30,14 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*','**/gradle-wrapper.properties') }} restore-keys: ${{ runner.os }}-gradle- - - name: Build & test with Gradle - run: ./gradlew clean testAll --no-daemon + - name: Build & test + run: ./gradlew clean test testAll --no-daemon - name: Upload test reports if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-results path: | **/build/test-results/**/*.xml **/build/reports/tests/** - - - name: Publish coverage to GitHub (optional) - if: success() - uses: codecov/codecov-action@v3 - with: - files: build/reports/jacoco/test/jacocoTestReport.xml \ No newline at end of file From eb812a7129c68dbdfff69f3e36bdc03a3f805161 Mon Sep 17 00:00:00 2001 From: Traqueur_ Date: Mon, 7 Jul 2025 16:20:16 +0200 Subject: [PATCH 5/5] fix(permission): add gradlew permission --- .github/workflows/test-all.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 018ac79..c4aeffc 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -15,6 +15,9 @@ jobs: - 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: