diff --git a/pom.xml b/pom.xml index d932b91ca..e29d7d061 100644 --- a/pom.xml +++ b/pom.xml @@ -198,6 +198,12 @@ FancyPlugins Repository https://repo.fancyplugins.de/releases + + + pyr-snapshots + Pyr's Repo + https://repo.pyr.lol/snapshots + @@ -406,6 +412,13 @@ 2.4.0 provided + + + lol.pyr + znpcsplus-api + 2.0.0-SNAPSHOT + provided + diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index ff315d7de..1cb28844a 100644 --- a/src/main/java/world/bentobox/bentobox/BentoBox.java +++ b/src/main/java/world/bentobox/bentobox/BentoBox.java @@ -19,7 +19,6 @@ import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.api.configuration.Config; import world.bentobox.bentobox.api.events.BentoBoxReadyEvent; -import world.bentobox.bentobox.api.hooks.Hook; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.Notifier; import world.bentobox.bentobox.api.user.User; @@ -33,6 +32,7 @@ import world.bentobox.bentobox.hooks.MyWorldsHook; import world.bentobox.bentobox.hooks.MythicMobsHook; import world.bentobox.bentobox.hooks.SlimefunHook; import world.bentobox.bentobox.hooks.VaultHook; +import world.bentobox.bentobox.hooks.ZNPCsPlusHook; import world.bentobox.bentobox.hooks.placeholders.PlaceholderAPIHook; import world.bentobox.bentobox.listeners.BannedCommands; import world.bentobox.bentobox.listeners.BlockEndDragon; @@ -196,6 +196,8 @@ public class BentoBox extends JavaPlugin implements Listener { // FancyNpcs hooksManager.registerHook(new FancyNpcsHook()); + // ZNPCsPlus + hooksManager.registerHook(new ZNPCsPlusHook()); // MythicMobs hooksManager.registerHook(new MythicMobsHook()); diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java index dc10da175..6d678bd08 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java @@ -45,6 +45,7 @@ import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity; import world.bentobox.bentobox.hooks.FancyNpcsHook; import world.bentobox.bentobox.hooks.MythicMobsHook; +import world.bentobox.bentobox.hooks.ZNPCsPlusHook; /** * The clipboard provides the holding spot for an active blueprint that is being @@ -71,6 +72,7 @@ public class BlueprintClipboard { private final BentoBox plugin = BentoBox.getInstance(); private Optional mmh; private Optional npc; + private Optional znpc; /** * Create a clipboard for blueprint @@ -82,12 +84,15 @@ public class BlueprintClipboard { } public BlueprintClipboard() { - // Citizens Hook + // Fancy NPCs Hook npc = plugin.getHooks().getHook("FancyNpcs").filter(FancyNpcsHook.class::isInstance) .map(FancyNpcsHook.class::cast); // MythicMobs Hook mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance) .map(MythicMobsHook.class::cast); + // ZNPCs Plus Hook + znpc = plugin.getHooks().getHook("ZNPCsPlus").filter(ZNPCsPlusHook.class::isInstance) + .map(ZNPCsPlusHook.class::cast); } /** @@ -143,6 +148,9 @@ public class BlueprintClipboard { // Add all the citizens for the area in one go. This is pretty fast. bpEntities.putAll(npc.get().getNpcsInArea(world, vectorsToCopy, origin)); } + if (znpc.isPresent()) { + bpEntities.putAll(znpc.get().getNpcsInArea(world, vectorsToCopy, origin)); + } // Repeating copy task copyTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java index 8ba1cb498..06a53e40a 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java @@ -240,7 +240,7 @@ public class BlueprintPaster { int x = location.getBlockX() + entry.getKey().getBlockX(); int y = location.getBlockY() + entry.getKey().getBlockY(); int z = location.getBlockZ() + entry.getKey().getBlockZ(); - Location center = new Location(world, x, y, z).add(new Vector(0.5, 0.5, 0.5)); + Location center = new Location(world, x, y, z).add(new Vector(0.5, 0D, 0.5)); List entities = entry.getValue(); entityMap.put(center, entities); count++; diff --git a/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java b/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java new file mode 100644 index 000000000..d9a31dfef --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java @@ -0,0 +1,104 @@ +package world.bentobox.bentobox.hooks; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; + +import lol.pyr.znpcsplus.api.NpcApiProvider; +import lol.pyr.znpcsplus.api.npc.NpcEntry; +import lol.pyr.znpcsplus.util.NpcLocation; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.hooks.Hook; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity; +import world.bentobox.bentobox.util.Util; + +/** + * Provides copy and pasting of ZNPCS Plus in blueprints https://github.com/Pyrbu/ZNPCsPlus + * + * @author tastybento + * @since 3.2.0 + */ +public class ZNPCsPlusHook extends Hook { + + private static final String VERSION = "2.0.0-SNAPSHOT"; // Minimum version required + + public ZNPCsPlusHook() { + super("ZNPCsPlus", Material.PLAYER_HEAD); + } + + public String serializeNPC(NpcEntry entry, Vector origin) { + String result = NpcApiProvider.get().getNpcSerializerRegistry().getSerializer(YamlConfiguration.class) + .serialize(entry) + .saveToString(); + return result; + } + + public boolean spawnNpc(String yaml, Location pos) throws InvalidConfigurationException { + YamlConfiguration yaml2 = new YamlConfiguration(); + yaml2.loadFromString(yaml); + NpcEntry entry = NpcApiProvider.get().getNpcSerializerRegistry().getSerializer(YamlConfiguration.class) + .deserialize(yaml2); + NpcLocation loc = new NpcLocation(pos); + entry.getNpc().setLocation(loc); + NpcApiProvider.get().getNpcRegistry().register(entry); + + return true; + } + + @Override + public boolean hook() { + boolean hooked = this.isPluginAvailable(); + // Check version + String version = this.getPlugin().getDescription().getVersion(); + if (!Util.isVersionCompatible(version, VERSION)) { + return false; + } + if (!hooked) { + BentoBox.getInstance().logError("Could not hook into FancyNpcs"); + } + return hooked; + } + + @Override + public String getFailureCause() { + // The only failure is wrong version + return "ZNPCsPlus version " + VERSION + " required or later. You are running " + + this.getPlugin().getDescription().getVersion(); + } + + public Map> getNpcsInArea(World world, List vectorsToCopy, + @Nullable Vector origin) { + Map> bpEntities = new HashMap<>(); + + for (NpcEntry npcEntry : NpcApiProvider.get().getNpcRegistry().getAll()) { + NpcLocation npcLocation = npcEntry.getNpc().getLocation(); + Vector loc = new Vector(npcLocation.getBlockX(), npcLocation.getBlockY(), npcLocation.getBlockZ()); + if (npcEntry.getNpc().getWorld().equals(world) && vectorsToCopy.contains(loc)) { + // Put the NPC into a BlueprintEntity - serialize it + BlueprintEntity cit = new BlueprintEntity(); + cit.setNpc(this.serializeNPC(npcEntry, origin)); + // Retrieve or create the list of entities and add this one + List entities = bpEntities.getOrDefault(loc, new ArrayList<>()); + entities.add(cit); + // Create the position where this entity will be pasted relative to the location + Vector origin2 = origin == null ? new Vector(0, 0, 0) : origin; + int x = loc.getBlockX() - origin2.getBlockX(); + int y = loc.getBlockY() - origin2.getBlockY(); + int z = loc.getBlockZ() - origin2.getBlockZ(); + Vector pos = new Vector(x, y, z); + // Store + bpEntities.put(pos, entities); // Update the map + } + } + return bpEntities; + } +} diff --git a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java index 86ee2e070..2e6083026 100644 --- a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java +++ b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java @@ -36,6 +36,7 @@ import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.hooks.FancyNpcsHook; import world.bentobox.bentobox.hooks.MythicMobsHook; +import world.bentobox.bentobox.hooks.ZNPCsPlusHook; import world.bentobox.bentobox.nms.PasteHandler; /** @@ -176,8 +177,8 @@ public class DefaultPasteUtil { public static CompletableFuture setEntity(Island island, Location location, List list) { World world = location.getWorld(); assert world != null; - return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null) - .forEach(k -> spawnBlueprintEntity(k, location, island))); + return Util.getChunkAtAsync(location) + .thenRun(() -> list.stream().forEach(k -> spawnBlueprintEntity(k, location, island))); } /** @@ -188,7 +189,7 @@ public class DefaultPasteUtil { * @return true if Bukkit entity spawned, false another plugin entity spawned */ static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island island) { - // Npc entity + // FancyNpc entity if (k.getNpc() != null && plugin.getHooks().getHook("FancyNpcs").filter(mmh -> mmh instanceof FancyNpcsHook).map(mmh -> { try { @@ -201,6 +202,19 @@ public class DefaultPasteUtil { // Npc has spawned. return false; } + // ZNPCsPlus + if (k.getNpc() != null + && plugin.getHooks().getHook("ZNPCsPlus").filter(mmh -> mmh instanceof ZNPCsPlusHook).map(znpch -> { + try { + return ((ZNPCsPlusHook) znpch).spawnNpc(k.getNpc(), location); + } catch (InvalidConfigurationException e) { + plugin.logError("ZNPCsPlus loading failed in blueprint."); + return false; + } + }).orElse(false)) { + // Npc has spawned. + return false; + } // Mythic Mobs entity if (k.getMythicMobsRecord() != null && plugin.getHooks().getHook("MythicMobs") @@ -210,6 +224,10 @@ public class DefaultPasteUtil { // MythicMob has spawned. return false; } + if (k.getType() == null) { + // Nothing + return false; + } LivingEntity e = (LivingEntity) location.getWorld().spawnEntity(location, k.getType()); if (k.getCustomName() != null) { String customName = k.getCustomName(); diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index 00434d260..3cf2fac55 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -514,6 +514,54 @@ public class Util { return PaperLib.getMinecraftPatchVersion(); } + /** + * Checks if the given version is compatible with the required version. + * + *

+ * A version is considered compatible if: + *

    + *
  • The major, minor, and patch components of the given version are greater than or equal to those of the required version.
  • + *
  • If the numeric components are equal, the absence of "-SNAPSHOT" in the given version takes precedence (i.e., release versions are considered more compatible than SNAPSHOT versions).
  • + *
+ *

+ * + * @param version the version to check, in the format "major.minor.patch[-SNAPSHOT]". + * @param requiredVersion the required version, in the format "major.minor.patch[-SNAPSHOT]". + * @return {@code true} if the given version is compatible with the required version; {@code false} otherwise. + * + *

+ * Examples: + *

    + *
  • {@code isVersionCompatible("2.1.0", "2.0.0-SNAPSHOT")} returns {@code true}
  • + *
  • {@code isVersionCompatible("2.0.0", "2.0.0-SNAPSHOT")} returns {@code true}
  • + *
  • {@code isVersionCompatible("2.0.0-SNAPSHOT", "2.0.0")} returns {@code false}
  • + *
  • {@code isVersionCompatible("1.9.9", "2.0.0-SNAPSHOT")} returns {@code false}
  • + *
+ *

+ */ + public static boolean isVersionCompatible(String version, String requiredVersion) { + String[] versionParts = version.replace("-SNAPSHOT", "").split("\\."); + String[] requiredVersionParts = requiredVersion.replace("-SNAPSHOT", "").split("\\."); + + for (int i = 0; i < Math.max(versionParts.length, requiredVersionParts.length); i++) { + int vPart = i < versionParts.length ? Integer.parseInt(versionParts[i]) : 0; + int rPart = i < requiredVersionParts.length ? Integer.parseInt(requiredVersionParts[i]) : 0; + + if (vPart > rPart) { + return true; + } else if (vPart < rPart) { + return false; + } + } + + // If numeric parts are equal, prioritize SNAPSHOT as lower precedence + boolean isVersionSnapshot = version.contains("-SNAPSHOT"); + boolean isRequiredSnapshot = requiredVersion.contains("-SNAPSHOT"); + + // If required version is a full release but current version is SNAPSHOT, it's incompatible + return !(!isRequiredSnapshot && isVersionSnapshot); + } + /** * Check if the server has access to the Spigot API * @return True for Spigot and Paper environments diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index fcb054854..9ddec656a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -24,6 +24,7 @@ softdepend: - LuckPerms - EconomyPlus - MythicMobs + - ZNPCsPlus libraries: - mysql:mysql-connector-java:${mysql.version} diff --git a/src/test/java/world/bentobox/bentobox/hooks/MythicMobsHookTest.java b/src/test/java/world/bentobox/bentobox/hooks/MythicMobsHookTest.java index 87fe88631..c9dc48437 100644 --- a/src/test/java/world/bentobox/bentobox/hooks/MythicMobsHookTest.java +++ b/src/test/java/world/bentobox/bentobox/hooks/MythicMobsHookTest.java @@ -21,7 +21,6 @@ import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -98,13 +97,6 @@ public class MythicMobsHookTest { hook = new MythicMobsHook(); } - /** - * @throws java.lang.Exception - */ - @After - public void tearDown() throws Exception { - } - /** * Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#hook()}. */ diff --git a/src/test/java/world/bentobox/bentobox/hooks/ZNPCsPlusHookTest.java b/src/test/java/world/bentobox/bentobox/hooks/ZNPCsPlusHookTest.java new file mode 100644 index 000000000..cdce3e51e --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/hooks/ZNPCsPlusHookTest.java @@ -0,0 +1,174 @@ +package world.bentobox.bentobox.hooks; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginManager; +import org.bukkit.util.Vector; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import lol.pyr.znpcsplus.api.NpcApi; +import lol.pyr.znpcsplus.api.NpcApiProvider; +import lol.pyr.znpcsplus.api.npc.Npc; +import lol.pyr.znpcsplus.api.npc.NpcEntry; +import lol.pyr.znpcsplus.api.npc.NpcRegistry; +import lol.pyr.znpcsplus.api.serialization.NpcSerializer; +import lol.pyr.znpcsplus.api.serialization.NpcSerializerRegistry; +import lol.pyr.znpcsplus.util.NpcLocation; +import world.bentobox.bentobox.BentoBox; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ BentoBox.class, Bukkit.class, NpcApiProvider.class }) +public class ZNPCsPlusHookTest { + + @Mock + private BentoBox plugin; + @Mock + private PluginManager pim; + @Mock + private Plugin mythicMobs; + @Mock + private Location location; + @Mock + private World world; + @Mock + private Plugin npcPlugin; + private ZNPCsPlusHook hook; + @Mock + private NpcEntry entry; + @Mock + private NpcApi npcApi; + @Mock + private NpcSerializerRegistry npcSerReg; + @Mock + private NpcSerializer ser; + @Mock + private NpcRegistry registry; + @Mock + private Npc npc; + @Mock + private NpcLocation npcLoc; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Bukkit + PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); + when(Bukkit.getPluginManager()).thenReturn(pim); + when(npcPlugin.getDescription()).thenReturn(new PluginDescriptionFile("ZNPCsPlus", "2.0.0-SNAPSHOT", "main")); + when(pim.getPlugin("ZNPCsPlus")).thenReturn(npcPlugin); + // Location + when(world.getName()).thenReturn("bskyblock"); + when(location.getWorld()).thenReturn(world); + // NpcApiProvider + PowerMockito.mockStatic(NpcApiProvider.class, Mockito.RETURNS_MOCKS); + when(NpcApiProvider.get()).thenReturn(npcApi); + + when(registry.getAll()).thenAnswer(invocation -> List.of(entry)); + + when(npcLoc.getBlockX()).thenReturn(0); + when(npcLoc.getBlockY()).thenReturn(0); + when(npcLoc.getBlockZ()).thenReturn(0); + when(npc.getWorld()).thenReturn(world); + + when(npc.getLocation()).thenReturn(npcLoc); + + when(npcApi.getNpcRegistry()).thenReturn(registry); + when(npcApi.getNpcSerializerRegistry()).thenReturn(npcSerReg); + when(npcSerReg.getSerializer(any())).thenReturn(ser); + YamlConfiguration yaml = new YamlConfiguration(); + yaml.set("test", "test"); + when(ser.serialize(any())).thenReturn(yaml); + when(entry.getNpc()).thenReturn(npc); + when(ser.deserialize(any())).thenReturn(entry); + + + hook = new ZNPCsPlusHook(); + } + + + /** + * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#hook()}. + */ + @Test + public void testHook() { + // Not hooked + assertFalse(hook.hook()); + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#getFailureCause()}. + */ + @Test + public void testGetFailureCause() { + when(npcPlugin.getDescription()).thenReturn(new PluginDescriptionFile("ZNPCsPlus", "1.0.0", "main")); + assertEquals("ZNPCsPlus version 2.0.0-SNAPSHOT required or later. You are running 1.0.0", + hook.getFailureCause()); + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#ZNPCsPlusHook()}. + */ + @Test + public void testZNPCsPlusHook() { + assertNotNull(hook); + assertEquals(Material.PLAYER_HEAD, hook.getIcon()); + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#serializeNPC(lol.pyr.znpcsplus.api.npc.NpcEntry, org.bukkit.util.Vector)}. + */ + @Test + public void testSerializeNPC() { + assertEquals("test: test\n", hook.serializeNPC(entry, new Vector(1, 1, 1))); + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#spawnNpc(java.lang.String, org.bukkit.Location)}. + */ + @Test + public void testSpawnNpc() { + try { + assertTrue(hook.spawnNpc("", location)); + } catch (InvalidConfigurationException e) { + e.printStackTrace(); + } + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#getNpcsInArea(org.bukkit.World, java.util.List, org.bukkit.util.Vector)}. + */ + @Test + public void testGetNpcsInArea() { + hook.getNpcsInArea(world, List.of(new Vector(0, 0, 0)), new Vector(0, 0, 0)); + } + +} diff --git a/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java b/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java index 38135c66d..768b1ad32 100644 --- a/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java +++ b/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java @@ -141,6 +141,9 @@ public class DefaultPasteUtilTest { when(plugin.getHooks()).thenReturn(hooksManager); when(plugin.getPlayers()).thenReturn(pm); + + // Blueprint Entity + when(blueprintEntity.getType()).thenReturn(EntityType.PLAYER); } /** diff --git a/src/test/java/world/bentobox/bentobox/util/UtilTest.java b/src/test/java/world/bentobox/bentobox/util/UtilTest.java index 4745b6ce3..d6162354d 100644 --- a/src/test/java/world/bentobox/bentobox/util/UtilTest.java +++ b/src/test/java/world/bentobox/bentobox/util/UtilTest.java @@ -494,4 +494,112 @@ public class UtilTest { assertEquals("§x§f§f§0§0§0§0full hex", Util.translateColorCodes("&#ff0000 full hex")); assertEquals("&#ggg outside hex range", Util.translateColorCodes("&#ggg outside hex range")); } -} + + /** + * Tests if the method returns true for identical versions without SNAPSHOT. + */ + @Test + public void testVersionIsCompatible_SameVersion() { + assertTrue("Same versions should be compatible", Util.isVersionCompatible("2.0.0", "2.0.0")); + } + + /** + * Tests if the method returns true for identical SNAPSHOT versions. + */ + @Test + public void testVersionIsCompatible_SnapshotToSnapshot() { + assertTrue("Same SNAPSHOT versions should be compatible", + Util.isVersionCompatible("2.0.0-SNAPSHOT", "2.0.0-SNAPSHOT")); + } + + /** + * Tests if the method considers release versions compatible with their SNAPSHOT equivalents. + */ + @Test + public void testVersionIsCompatible_ReleaseGreaterThanSnapshot() { + assertTrue("Release version should be compatible with SNAPSHOT of the same version", + Util.isVersionCompatible("2.0.0", "2.0.0-SNAPSHOT")); + } + + /** + * Tests if the method considers SNAPSHOT versions less compatible than release versions. + */ + @Test + public void testVersionIsCompatible_SnapshotLessThanRelease() { + assertFalse("SNAPSHOT version should not be compatible with release of the same version", + Util.isVersionCompatible("2.0.0-SNAPSHOT", "2.0.0")); + } + + /** + * Tests if the method correctly identifies compatibility for a higher major version. + */ + @Test + public void testVersionIsCompatible_MajorVersionGreater() { + assertTrue("Higher major version should be compatible", Util.isVersionCompatible("3.0.0", "2.0.0")); + } + + /** + * Tests if the method correctly identifies incompatibility for a lower major version. + */ + @Test + public void testVersionIsCompatible_MajorVersionLower() { + assertFalse("Lower major version should not be compatible", Util.isVersionCompatible("1.9.9", "2.0.0")); + } + + /** + * Tests if the method correctly identifies compatibility for a higher minor version. + */ + @Test + public void testVersionIsCompatible_MinorVersionGreater() { + assertTrue("Higher minor version should be compatible", Util.isVersionCompatible("2.1.0", "2.0.0")); + } + + /** + * Tests if the method correctly identifies incompatibility for a lower minor version. + */ + @Test + public void testVersionIsCompatible_MinorVersionLower() { + assertFalse("Lower minor version should not be compatible", Util.isVersionCompatible("2.0.0", "2.1.0")); + } + + /** + * Tests if the method correctly identifies compatibility for a higher patch version. + */ + @Test + public void testVersionIsCompatible_PatchVersionGreater() { + assertTrue("Higher patch version should be compatible", Util.isVersionCompatible("2.0.1", "2.0.0")); + } + + /** + * Tests if the method correctly identifies incompatibility for a lower patch version. + */ + @Test + public void testVersionIsCompatible_PatchVersionLower() { + assertFalse("Lower patch version should not be compatible", Util.isVersionCompatible("2.0.0", "2.0.1")); + } + + /** + * Tests if the method correctly handles compatibility when both versions have a SNAPSHOT suffix. + */ + @Test + public void testVersionIsCompatible_HandlesSnapshotSuffix() { + assertTrue("Higher patch version (SNAPSHOT) should be compatible with lower patch version (SNAPSHOT)", + Util.isVersionCompatible("2.0.1-SNAPSHOT", "2.0.0-SNAPSHOT")); + } + + /** + * Tests if the method throws an exception for an empty version string. + */ + @Test(expected = NumberFormatException.class) + public void testVersionIsCompatible_EmptyVersion() { + Util.isVersionCompatible("", "2.0.0"); + } + + /** + * Tests if the method throws an exception for a null version string. + */ + @Test(expected = NullPointerException.class) + public void testVersionIsCompatible_NullVersion() { + Util.isVersionCompatible(null, "2.0.0"); + } +} \ No newline at end of file