diff --git a/core/src/main/java/github/nighter/smartspawner/hooks/IntegrationManager.java b/core/src/main/java/github/nighter/smartspawner/hooks/IntegrationManager.java index fb44e0ea..41faf6dd 100644 --- a/core/src/main/java/github/nighter/smartspawner/hooks/IntegrationManager.java +++ b/core/src/main/java/github/nighter/smartspawner/hooks/IntegrationManager.java @@ -244,16 +244,18 @@ private void checkIntegrationPlugins() { }, true); } - private boolean checkPlugin(String pluginName, PluginCheck checker, boolean logSuccess) { + boolean checkPlugin(String pluginName, PluginCheck checker, boolean logSuccess) { try { if (checker.check()) { - if (logSuccess) { + if (logSuccess && plugin != null && plugin.getLogger() != null) { plugin.getLogger().info(pluginName + " integration enabled successfully!"); } return true; } - } catch (Exception e) { - plugin.getLogger().log(Level.WARNING, "Failed to initialize " + pluginName + " integration", e); + } catch (Throwable t) { + if (plugin != null && plugin.getLogger() != null) { + plugin.getLogger().log(Level.WARNING, "Failed to initialize " + pluginName + " integration", t); + } } return false; } @@ -265,7 +267,7 @@ public void reload() { } @FunctionalInterface - private interface PluginCheck { + interface PluginCheck { boolean check(); } } diff --git a/core/src/main/java/github/nighter/smartspawner/hooks/protections/api/WorldGuard.java b/core/src/main/java/github/nighter/smartspawner/hooks/protections/api/WorldGuard.java index f8ecdaaf..a34c358d 100644 --- a/core/src/main/java/github/nighter/smartspawner/hooks/protections/api/WorldGuard.java +++ b/core/src/main/java/github/nighter/smartspawner/hooks/protections/api/WorldGuard.java @@ -20,12 +20,16 @@ public boolean canBreak(@NotNull Player player, @NotNull org.bukkit.Location loc return true; } - LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); - Location loc = new Location(BukkitAdapter.adapt(location.getWorld()), location.getX(), location.getY(), location.getZ()); - RegionContainer container = com.sk89q.worldguard.WorldGuard.getInstance().getPlatform().getRegionContainer(); - RegionQuery query = container.createQuery(); + try { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + Location loc = new Location(BukkitAdapter.adapt(location.getWorld()), location.getX(), location.getY(), location.getZ()); + RegionContainer container = com.sk89q.worldguard.WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionQuery query = container.createQuery(); - return query.testBuild(loc, localPlayer, Flags.BLOCK_BREAK); + return query.testBuild(loc, localPlayer, Flags.BLOCK_BREAK); + } catch (Throwable t) { + return true; + } } @Override @@ -34,12 +38,16 @@ public boolean canStack(@NotNull Player player, @NotNull org.bukkit.Location loc return true; } - LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapOfflinePlayer(player); - Location loc = new Location(BukkitAdapter.adapt(location.getWorld()), location.getX(), location.getY(), location.getZ()); - RegionContainer container = com.sk89q.worldguard.WorldGuard.getInstance().getPlatform().getRegionContainer(); - RegionQuery query = container.createQuery(); + try { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + Location loc = new Location(BukkitAdapter.adapt(location.getWorld()), location.getX(), location.getY(), location.getZ()); + RegionContainer container = com.sk89q.worldguard.WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionQuery query = container.createQuery(); - return query.testBuild(loc, localPlayer, Flags.BLOCK_PLACE); + return query.testBuild(loc, localPlayer, Flags.BLOCK_PLACE); + } catch (Throwable t) { + return true; + } } @Override @@ -48,11 +56,15 @@ public boolean canOpenMenu(@NotNull Player player, @NotNull org.bukkit.Location return true; } - LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); - Location loc = new Location(BukkitAdapter.adapt(location.getWorld()), location.getX(), location.getY(), location.getZ()); - RegionContainer container = com.sk89q.worldguard.WorldGuard.getInstance().getPlatform().getRegionContainer(); - RegionQuery query = container.createQuery(); + try { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + Location loc = new Location(BukkitAdapter.adapt(location.getWorld()), location.getX(), location.getY(), location.getZ()); + RegionContainer container = com.sk89q.worldguard.WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionQuery query = container.createQuery(); - return query.testBuild(loc, localPlayer, Flags.INTERACT); + return query.testBuild(loc, localPlayer, Flags.INTERACT); + } catch (Throwable t) { + return true; + } } } diff --git a/core/src/main/resources/paper-plugin.yml b/core/src/main/resources/paper-plugin.yml index e1d5cb13..a4a8fd51 100644 --- a/core/src/main/resources/paper-plugin.yml +++ b/core/src/main/resources/paper-plugin.yml @@ -49,6 +49,14 @@ dependencies: load: BEFORE required: false join-classpath: true + WorldEdit: + load: BEFORE + required: false + join-classpath: true + FastAsyncWorldEdit: + load: BEFORE + required: false + join-classpath: true GriefPrevention: load: BEFORE required: false diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index 22008781..78ac6672 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -18,6 +18,8 @@ softdepend: # Protection/Region Plugins - WorldGuard + - WorldEdit + - FastAsyncWorldEdit - GriefPrevention - Lands - Towny diff --git a/core/src/test/java/github/nighter/smartspawner/hooks/IntegrationManagerTest.java b/core/src/test/java/github/nighter/smartspawner/hooks/IntegrationManagerTest.java new file mode 100644 index 00000000..9f00a861 --- /dev/null +++ b/core/src/test/java/github/nighter/smartspawner/hooks/IntegrationManagerTest.java @@ -0,0 +1,91 @@ +package github.nighter.smartspawner.hooks; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class IntegrationManagerTest { + + @Test + @DisplayName("checkPlugin safely catches NoClassDefFoundError and returns false") + void checkPluginSwallowsNoClassDefFoundError() { + IntegrationManager manager = new IntegrationManager(null); + + boolean result = assertDoesNotThrow(() -> + manager.checkPlugin("WorldGuard", () -> { + throw new NoClassDefFoundError("com/sk89q/worldedit/extent/Extent"); + }, true) + ); + + assertFalse(result, "Integration check must return false when a class is missing"); + } + + @Test + @DisplayName("checkPlugin safely catches generic LinkageError and returns false") + void checkPluginSwallowsGenericLinkageError() { + IntegrationManager manager = new IntegrationManager(null); + + boolean result = assertDoesNotThrow(() -> + manager.checkPlugin("TestPlugin", () -> { + throw new LinkageError("Incompatible class change"); + }, true) + ); + + assertFalse(result, "Integration check must return false when linkage fails"); + } + + @Test + @DisplayName("checkPlugin returns true when check succeeds") + void checkPluginReturnsTrueOnSuccess() { + IntegrationManager manager = new IntegrationManager(null); + + boolean result = manager.checkPlugin("TestPlugin", () -> true, true); + + assertTrue(result, "Integration check must return true when check succeeds"); + } + + @Test + @DisplayName("paper-plugin.yml declares WorldEdit and FastAsyncWorldEdit with join-classpath: true") + void paperPluginYmlDeclaresWorldEditClasspath() { + InputStream stream = getClass().getClassLoader().getResourceAsStream("paper-plugin.yml"); + assertNotNull(stream, "paper-plugin.yml must exist on classpath"); + + YamlConfiguration config = YamlConfiguration.loadConfiguration( + new InputStreamReader(stream, StandardCharsets.UTF_8)); + + assertTrue(config.getBoolean("dependencies.server.WorldEdit.join-classpath"), + "WorldEdit must have join-classpath: true in paper-plugin.yml"); + assertTrue(config.getBoolean("dependencies.server.FastAsyncWorldEdit.join-classpath"), + "FastAsyncWorldEdit must have join-classpath: true in paper-plugin.yml"); + assertTrue(config.getBoolean("dependencies.server.WorldGuard.join-classpath"), + "WorldGuard must have join-classpath: true in paper-plugin.yml"); + } + + @Test + @DisplayName("plugin.yml declares WorldEdit and FastAsyncWorldEdit in softdepend") + void pluginYmlDeclaresWorldEditSoftDepend() { + InputStream stream = getClass().getClassLoader().getResourceAsStream("plugin.yml"); + assertNotNull(stream, "plugin.yml must exist on classpath"); + + YamlConfiguration config = YamlConfiguration.loadConfiguration( + new InputStreamReader(stream, StandardCharsets.UTF_8)); + + List softdepend = config.getStringList("softdepend"); + assertTrue(softdepend.contains("WorldEdit"), + "WorldEdit must be in plugin.yml softdepend list"); + assertTrue(softdepend.contains("FastAsyncWorldEdit"), + "FastAsyncWorldEdit must be in plugin.yml softdepend list"); + assertTrue(softdepend.contains("WorldGuard"), + "WorldGuard must be in plugin.yml softdepend list"); + } +}