diff --git a/annotations-addon/src/test/java/fr/traqueur/commands/annotations/AnnotationCommandProcessorTest.java b/annotations-addon/src/test/java/fr/traqueur/commands/annotations/AnnotationCommandProcessorTest.java index e548aac..cd3ddc9 100644 --- a/annotations-addon/src/test/java/fr/traqueur/commands/annotations/AnnotationCommandProcessorTest.java +++ b/annotations-addon/src/test/java/fr/traqueur/commands/annotations/AnnotationCommandProcessorTest.java @@ -387,15 +387,97 @@ void commandsShouldBeSortedByDepth() { processor.register(commands); List labels = platform.getRegisteredLabels(); - + int adminIndex = labels.indexOf("admin"); int adminReloadIndex = labels.indexOf("admin.reload"); int adminReloadConfigIndex = labels.indexOf("admin.reload.config"); - - assertTrue(adminIndex < adminReloadIndex, + + assertTrue(adminIndex < adminReloadIndex, "admin should be registered before admin.reload"); assertTrue(adminReloadIndex < adminReloadConfigIndex, "admin.reload should be registered before admin.reload.config"); } } + + @Nested + @DisplayName("Primitive Types Support") + class PrimitiveTypesSupport { + + @Test + @DisplayName("should register command with primitive int argument") + void shouldRegisterCommandWithPrimitiveInt() { + PrimitiveTypesTestCommands commands = new PrimitiveTypesTestCommands(); + processor.register(commands); + + assertTrue(platform.hasCommand("primitiveint")); + Command cmd = platform.getCommand("primitiveint"); + assertNotNull(cmd); + + List> args = cmd.getArgs(); + assertEquals(1, args.size()); + assertEquals("value", args.get(0).name()); + } + + @Test + @DisplayName("should register command with primitive long argument") + void shouldRegisterCommandWithPrimitiveLong() { + PrimitiveTypesTestCommands commands = new PrimitiveTypesTestCommands(); + processor.register(commands); + + assertTrue(platform.hasCommand("primitivelong")); + Command cmd = platform.getCommand("primitivelong"); + assertNotNull(cmd); + + List> args = cmd.getArgs(); + assertEquals(1, args.size()); + assertEquals("value", args.get(0).name()); + } + + @Test + @DisplayName("should register command with primitive double argument") + void shouldRegisterCommandWithPrimitiveDouble() { + PrimitiveTypesTestCommands commands = new PrimitiveTypesTestCommands(); + processor.register(commands); + + assertTrue(platform.hasCommand("primitivedouble")); + Command cmd = platform.getCommand("primitivedouble"); + assertNotNull(cmd); + + List> args = cmd.getArgs(); + assertEquals(1, args.size()); + assertEquals("value", args.get(0).name()); + } + + @Test + @DisplayName("should register command with primitive boolean argument") + void shouldRegisterCommandWithPrimitiveBoolean() { + PrimitiveTypesTestCommands commands = new PrimitiveTypesTestCommands(); + processor.register(commands); + + assertTrue(platform.hasCommand("primitivebool")); + Command cmd = platform.getCommand("primitivebool"); + assertNotNull(cmd); + + List> args = cmd.getArgs(); + assertEquals(1, args.size()); + assertEquals("enabled", args.get(0).name()); + } + + @Test + @DisplayName("should register command with mixed primitive types") + void shouldRegisterCommandWithMixedPrimitives() { + PrimitiveTypesTestCommands commands = new PrimitiveTypesTestCommands(); + processor.register(commands); + + assertTrue(platform.hasCommand("mixedprimitives")); + Command cmd = platform.getCommand("mixedprimitives"); + assertNotNull(cmd); + + List> args = cmd.getArgs(); + assertEquals(3, args.size()); + assertEquals("count", args.get(0).name()); + assertEquals("enabled", args.get(1).name()); + assertEquals("ratio", args.get(2).name()); + } + } } \ No newline at end of file diff --git a/annotations-addon/src/test/java/fr/traqueur/commands/annotations/PrimitiveTypesIntegrationTest.java b/annotations-addon/src/test/java/fr/traqueur/commands/annotations/PrimitiveTypesIntegrationTest.java new file mode 100644 index 0000000..29b86c8 --- /dev/null +++ b/annotations-addon/src/test/java/fr/traqueur/commands/annotations/PrimitiveTypesIntegrationTest.java @@ -0,0 +1,225 @@ +package fr.traqueur.commands.annotations; + +import fr.traqueur.commands.annotations.commands.PrimitiveTypesTestCommands; +import fr.traqueur.commands.api.arguments.Arguments; +import fr.traqueur.commands.api.models.Command; +import fr.traqueur.commands.test.mocks.MockCommandManager; +import fr.traqueur.commands.test.mocks.MockPlatform; +import fr.traqueur.commands.test.mocks.MockSender; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration tests for primitive types support. + * These tests verify that commands with primitive type arguments can be: + * 1. Parsed correctly + * 2. Stored in Arguments + * 3. Retrieved from Arguments + * 4. Passed to method invocation via reflection + */ +@DisplayName("Primitive Types Integration") +class PrimitiveTypesIntegrationTest { + + private MockPlatform platform; + private MockCommandManager manager; + private PrimitiveTypesTestCommands commands; + + // Mock sender implementation for testing + private static class TestSender implements MockSender { + @Override + public void sendMessage(String message) { + // Do nothing + } + + @Override + public boolean hasPermission(String permission) { + return true; + } + } + + @BeforeEach + void setUp() { + platform = new MockPlatform(); + manager = new MockCommandManager(platform); + AnnotationCommandProcessor processor = new AnnotationCommandProcessor<>(manager); + commands = new PrimitiveTypesTestCommands(); + processor.register(commands); + } + + @Test + @DisplayName("should execute command with primitive int") + void shouldExecuteCommandWithPrimitiveInt() throws Exception { + Command cmd = platform.getCommand("primitiveint"); + assertNotNull(cmd); + + // Parse arguments + Arguments args = manager.parse(cmd, new String[]{"42"}); + assertNotNull(args); + assertEquals(1, args.size()); + + // Verify the argument was parsed and stored correctly + Integer value = args.get("value"); + assertEquals(42, value); + + // Execute the command + TestSender sender = new TestSender(); + cmd.execute(sender, args); + + // Verify the method was called + assertEquals(1, commands.executedCommands.size()); + assertEquals("primitiveint", commands.executedCommands.getFirst()); + + // Verify arguments were passed correctly + Object[] invokeArgs = commands.executedArgs.getFirst(); + assertEquals(2, invokeArgs.length); + assertEquals(sender, invokeArgs[0]); + assertEquals(42, invokeArgs[1]); + assertInstanceOf(Integer.class, invokeArgs[1]); + } + + @Test + @DisplayName("should execute command with primitive boolean") + void shouldExecuteCommandWithPrimitiveBoolean() throws Exception { + Command cmd = platform.getCommand("primitivebool"); + assertNotNull(cmd); + + // Parse arguments + Arguments args = manager.parse(cmd, new String[]{"true"}); + assertNotNull(args); + + // Verify the argument was parsed and stored correctly + Boolean enabled = args.get("enabled"); + assertTrue(enabled); + + // Execute the command + TestSender sender = new TestSender(); + cmd.execute(sender, args); + + // Verify the method was called correctly + assertEquals(1, commands.executedCommands.size()); + Object[] invokeArgs = commands.executedArgs.getFirst(); + assertEquals(true, invokeArgs[1]); + assertInstanceOf(Boolean.class, invokeArgs[1]); + } + + @Test + @DisplayName("should execute command with primitive double") + void shouldExecuteCommandWithPrimitiveDouble() throws Exception { + Command cmd = platform.getCommand("primitivedouble"); + assertNotNull(cmd); + + // Parse arguments + Arguments args = manager.parse(cmd, new String[]{"3.14"}); + assertNotNull(args); + + // Verify the argument was parsed and stored correctly + Double value = args.get("value"); + assertEquals(3.14, value, 0.001); + + // Execute the command + TestSender sender = new TestSender(); + cmd.execute(sender, args); + + // Verify the method was called correctly + assertEquals(1, commands.executedCommands.size()); + Object[] invokeArgs = commands.executedArgs.getFirst(); + assertInstanceOf(Double.class, invokeArgs[1]); + assertEquals(3.14, (Double) invokeArgs[1], 0.001); + } + + @Test + @DisplayName("should execute command with primitive long") + void shouldExecuteCommandWithPrimitiveLong() throws Exception { + Command cmd = platform.getCommand("primitivelong"); + assertNotNull(cmd); + + // Parse arguments + Arguments args = manager.parse(cmd, new String[]{"9223372036854775807"}); + assertNotNull(args); + + // Verify the argument was parsed and stored correctly + Long value = args.get("value"); + assertEquals(9223372036854775807L, value); + + // Execute the command + TestSender sender = new TestSender(); + cmd.execute(sender, args); + + // Verify the method was called correctly + assertEquals(1, commands.executedCommands.size()); + Object[] invokeArgs = commands.executedArgs.getFirst(); + assertEquals(9223372036854775807L, invokeArgs[1]); + assertInstanceOf(Long.class, invokeArgs[1]); + } + + @Test + @DisplayName("should execute command with mixed primitive types") + void shouldExecuteCommandWithMixedPrimitives() throws Exception { + Command cmd = platform.getCommand("mixedprimitives"); + assertNotNull(cmd); + + // Parse arguments + Arguments args = manager.parse(cmd, new String[]{"10", "true", "2.5"}); + assertNotNull(args); + assertEquals(3, args.size()); + + // Verify all arguments were parsed and stored correctly + Integer count = args.get("count"); + Boolean enabled = args.get("enabled"); + Double ratio = args.get("ratio"); + + assertEquals(10, count); + assertTrue(enabled); + assertEquals(2.5, ratio, 0.001); + + // Execute the command + TestSender sender = new TestSender(); + cmd.execute(sender, args); + + // Verify the method was called correctly with all arguments + assertEquals(1, commands.executedCommands.size()); + assertEquals("mixedprimitives", commands.executedCommands.getFirst()); + + Object[] invokeArgs = commands.executedArgs.getFirst(); + assertEquals(4, invokeArgs.length); + assertEquals(sender, invokeArgs[0]); + assertEquals(10, invokeArgs[1]); + assertEquals(true, invokeArgs[2]); + assertEquals(2.5, (Double) invokeArgs[3], 0.001); + } + + @Test + @DisplayName("should handle negative primitive int") + void shouldHandleNegativePrimitiveInt() throws Exception { + Command cmd = platform.getCommand("primitiveint"); + Arguments args = manager.parse(cmd, new String[]{"-42"}); + + Integer value = args.get("value"); + assertEquals(-42, value); + + TestSender sender = new TestSender(); + cmd.execute(sender, args); + + Object[] invokeArgs = commands.executedArgs.getFirst(); + assertEquals(-42, invokeArgs[1]); + } + + @Test + @DisplayName("should handle false boolean primitive") + void shouldHandleFalseBooleanPrimitive() throws Exception { + Command cmd = platform.getCommand("primitivebool"); + Arguments args = manager.parse(cmd, new String[]{"false"}); + + Boolean enabled = args.get("enabled"); + assertFalse(enabled); + + TestSender sender = new TestSender(); + cmd.execute(sender, args); + + Object[] invokeArgs = commands.executedArgs.getFirst(); + assertEquals(false, invokeArgs[1]); + } +} \ No newline at end of file diff --git a/annotations-addon/src/test/java/fr/traqueur/commands/annotations/commands/PrimitiveTypesTestCommands.java b/annotations-addon/src/test/java/fr/traqueur/commands/annotations/commands/PrimitiveTypesTestCommands.java new file mode 100644 index 0000000..a425449 --- /dev/null +++ b/annotations-addon/src/test/java/fr/traqueur/commands/annotations/commands/PrimitiveTypesTestCommands.java @@ -0,0 +1,53 @@ +package fr.traqueur.commands.annotations.commands; + +import fr.traqueur.commands.annotations.Arg; +import fr.traqueur.commands.annotations.Command; +import fr.traqueur.commands.annotations.CommandContainer; +import fr.traqueur.commands.test.mocks.MockSender; + +import java.util.ArrayList; +import java.util.List; + +/** + * Test commands using primitive types (int, boolean, double, long) instead of wrappers. + * This tests that the ArgumentConverter.Wrapper properly normalizes primitive types to wrappers. + */ +@CommandContainer +public class PrimitiveTypesTestCommands { + + public final List executedCommands = new ArrayList<>(); + public final List executedArgs = new ArrayList<>(); + + @Command(name = "primitiveint", description = "Test command with primitive int") + public void primitiveIntCommand(MockSender sender, @Arg("value") int value) { + executedCommands.add("primitiveint"); + executedArgs.add(new Object[]{sender, value}); + } + + @Command(name = "primitivelong", description = "Test command with primitive long") + public void primitiveLongCommand(MockSender sender, @Arg("value") long value) { + executedCommands.add("primitivelong"); + executedArgs.add(new Object[]{sender, value}); + } + + @Command(name = "primitivedouble", description = "Test command with primitive double") + public void primitiveDoubleCommand(MockSender sender, @Arg("value") double value) { + executedCommands.add("primitivedouble"); + executedArgs.add(new Object[]{sender, value}); + } + + @Command(name = "primitivebool", description = "Test command with primitive boolean") + public void primitiveBooleanCommand(MockSender sender, @Arg("enabled") boolean enabled) { + executedCommands.add("primitivebool"); + executedArgs.add(new Object[]{sender, enabled}); + } + + @Command(name = "mixedprimitives", description = "Test command with multiple primitive types") + public void mixedPrimitivesCommand(MockSender sender, + @Arg("count") int count, + @Arg("enabled") boolean enabled, + @Arg("ratio") double ratio) { + executedCommands.add("mixedprimitives"); + executedArgs.add(new Object[]{sender, count, enabled, ratio}); + } +} \ No newline at end of file diff --git a/core/src/main/java/fr/traqueur/commands/api/arguments/ArgumentConverter.java b/core/src/main/java/fr/traqueur/commands/api/arguments/ArgumentConverter.java index 51d9990..22dc091 100644 --- a/core/src/main/java/fr/traqueur/commands/api/arguments/ArgumentConverter.java +++ b/core/src/main/java/fr/traqueur/commands/api/arguments/ArgumentConverter.java @@ -22,12 +22,41 @@ public interface ArgumentConverter extends Function { record Wrapper(Class clazz, ArgumentConverter converter) { + /** + * Convert primitive types to their wrapper equivalents. + * This is necessary because Arguments storage uses Class.isInstance(), + * which doesn't work with primitive types (they have no instances). + * + * @param type the type to normalize + * @return the wrapper type if input was primitive, otherwise the input type unchanged + */ + @SuppressWarnings("unchecked") + private static Class toWrapperType(Class type) { + if (!type.isPrimitive()) { + return type; + } + + if (type == int.class) return (Class) Integer.class; + if (type == long.class) return (Class) Long.class; + if (type == double.class) return (Class) Double.class; + if (type == float.class) return (Class) Float.class; + if (type == boolean.class) return (Class) Boolean.class; + if (type == byte.class) return (Class) Byte.class; + if (type == short.class) return (Class) Short.class; + if (type == char.class) return (Class) Character.class; + if (type == void.class) return (Class) Void.class; + + return type; + } + public boolean convertAndApply(String input, String name, Arguments arguments) { T result = converter.apply(input); if (result == null) { return false; } - arguments.add(name, clazz, result); + // Always store with wrapper type to ensure Class.isInstance() works correctly + Class storageType = toWrapperType(clazz); + arguments.add(name, storageType, result); return true; } diff --git a/gradle.properties b/gradle.properties index 8656a1e..1e71c76 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=5.0.1 \ No newline at end of file +version=5.0.2