diff --git a/pom.xml b/pom.xml index 471fcd1..ffe6007 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ 5.10.2 5.11.0 - v1.21-SNAPSHOT + 4.110.0 1.21.11-R0.1-SNAPSHOT 3.14.0 @@ -53,7 +53,7 @@ ${build.version}-SNAPSHOT - 1.6.1 + 1.6.2 -LOCAL BentoBoxWorld_Challenges @@ -205,8 +205,8 @@ - com.github.MockBukkit - MockBukkit + org.mockbukkit.mockbukkit + mockbukkit-v1.21 ${mock-bukkit.version} test diff --git a/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java b/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java index 7b461c0..ccb6d46 100644 --- a/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java +++ b/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java @@ -736,7 +736,7 @@ public void importDatabaseFile(User user, World world, String fileName) } catch (Exception e) { - this.addon.getPlugin().logStacktrace(e); + reportImportFailure(user, world, e); return; } @@ -745,6 +745,21 @@ public void importDatabaseFile(User user, World world, String fileName) } + /** + * Logs the stacktrace and, if a player triggered the import, surfaces a chat error + * so they know to look at the server console. + */ + private void reportImportFailure(User user, World world, Exception e) + { + this.addon.getPlugin().logStacktrace(e); + if (user != null && user.isPlayer()) + { + Utils.sendMessage(user, world, Constants.ERRORS + "import-failed", + "[message]", e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()); + } + } + + /** * This method loads downloaded challenges into memory. * @param user User who calls downloaded challenge loading @@ -813,7 +828,7 @@ public void loadDownloadedChallenges(User user, World world, String downloadStri } catch (Exception e) { - this.addon.getPlugin().logStacktrace(e); + reportImportFailure(user, world, e); return; } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 581b345..e6e662a 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1232,6 +1232,7 @@ challenges: island-level: "Your island must be at least level [number] to complete this challenge!" no-load: "Error: Could not load [file]. Error: [message]." load-error: "Error: Cannot load [value]." + import-failed: "Could not import challenges: [message]. Check the server console for details." no-rank: "You do not have a high enough rank to do that." cannot-remove-items: "Some items cannot be removed from your inventory!" exist-challenges-or-levels: "Challenges already exist in your world. Cannot proceed!" diff --git a/src/test/java/world/bentobox/challenges/ChallengesAddonTest.java b/src/test/java/world/bentobox/challenges/ChallengesAddonTest.java index 1e2bd48..02ac704 100644 --- a/src/test/java/world/bentobox/challenges/ChallengesAddonTest.java +++ b/src/test/java/world/bentobox/challenges/ChallengesAddonTest.java @@ -42,12 +42,15 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; +import world.bentobox.challenges.panel.PanelTestHelper; + import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.addons.Addon; @@ -101,6 +104,15 @@ public class ChallengesAddonTest { @BeforeEach public void setUp() throws Exception { closeable = MockitoAnnotations.openMocks(this); + // Force Bukkit's Tag. to run against a real MockBukkit ServerMock before + // we install the Mockito static mock below. Without this, Tag. can later + // fire while Bukkit is statically mocked, permanently null-ing every Tag constant + // for the JVM and corrupting any subsequent test that creates an ItemStack + // (e.g. CommonPagedPanelTest -> ItemType. -> MaterialTags. -> + // Objects.requireNonNull(Tag.ALL_SIGNS) -> NPE). + MockBukkit.mock(); + PanelTestHelper.primeBukkitRegistry(); + MockBukkit.unmock(); // Set up plugin WhiteBox.setInternalState(BentoBox.class, "instance", plugin); when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); diff --git a/src/test/java/world/bentobox/challenges/panel/CommonPagedPanelTest.java b/src/test/java/world/bentobox/challenges/panel/CommonPagedPanelTest.java index ece17b3..6015aeb 100644 --- a/src/test/java/world/bentobox/challenges/panel/CommonPagedPanelTest.java +++ b/src/test/java/world/bentobox/challenges/panel/CommonPagedPanelTest.java @@ -116,6 +116,7 @@ public int getPageIndex() throws Exception { void setUp() { closeable = MockitoAnnotations.openMocks(this); ServerMock mbServer = MockBukkit.mock(); + PanelTestHelper.primeBukkitRegistry(); when(addon.getChallengesManager()).thenReturn(manager); PanelTestHelper.setupUserTranslations(user); diff --git a/src/test/java/world/bentobox/challenges/panel/CommonPanelTest.java b/src/test/java/world/bentobox/challenges/panel/CommonPanelTest.java index 337ab89..654be42 100644 --- a/src/test/java/world/bentobox/challenges/panel/CommonPanelTest.java +++ b/src/test/java/world/bentobox/challenges/panel/CommonPanelTest.java @@ -78,6 +78,7 @@ public PanelItem getReturnButton() { void setUp() { closeable = MockitoAnnotations.openMocks(this); ServerMock mbServer = MockBukkit.mock(); + PanelTestHelper.primeBukkitRegistry(); when(addon.getChallengesManager()).thenReturn(manager); PanelTestHelper.setupUserTranslations(user); diff --git a/src/test/java/world/bentobox/challenges/panel/ConversationUtilsTest.java b/src/test/java/world/bentobox/challenges/panel/ConversationUtilsTest.java index b8f9ff6..4125ea1 100644 --- a/src/test/java/world/bentobox/challenges/panel/ConversationUtilsTest.java +++ b/src/test/java/world/bentobox/challenges/panel/ConversationUtilsTest.java @@ -45,6 +45,7 @@ class ConversationUtilsTest { void setUp() { closeable = MockitoAnnotations.openMocks(this); ServerMock mbServer = MockBukkit.mock(); + PanelTestHelper.primeBukkitRegistry(); when(user.getTranslation(anyString())).thenAnswer( (Answer) inv -> inv.getArgument(0, String.class)); diff --git a/src/test/java/world/bentobox/challenges/panel/PanelTestHelper.java b/src/test/java/world/bentobox/challenges/panel/PanelTestHelper.java index 39552a3..d496ae8 100644 --- a/src/test/java/world/bentobox/challenges/panel/PanelTestHelper.java +++ b/src/test/java/world/bentobox/challenges/panel/PanelTestHelper.java @@ -22,6 +22,23 @@ */ public class PanelTestHelper { + /** + * Force Bukkit's material registry to be populated while MockBukkit's mock + * server is active. Call this immediately after `MockBukkit.mock()`. + * + *

Why: `org.bukkit.inventory.ItemType.<clinit>` iterates over Material and + * looks each entry up in the registry. If it runs before MockBukkit has populated + * the registry (e.g., when a panel test that doesn't extend AbstractChallengesTest + * runs first on CI's filesystem ordering), it throws NoSuchElementException, and + * ItemStackMock stays in an errored state for the rest of the JVM — poisoning every + * subsequent test. Touching `Tag.LEAVES` forces the registry to populate before + * ItemType loads. + */ + public static void primeBukkitRegistry() { + @SuppressWarnings("unused") + var unused = org.bukkit.Tag.LEAVES; + } + /** * Set up user translation mocks to return the key for any invocation. * Uses a lenient default answer that returns the first String argument diff --git a/src/test/java/world/bentobox/challenges/panel/admin/AdminPanelTest.java b/src/test/java/world/bentobox/challenges/panel/admin/AdminPanelTest.java index 08d910e..bdefd47 100644 --- a/src/test/java/world/bentobox/challenges/panel/admin/AdminPanelTest.java +++ b/src/test/java/world/bentobox/challenges/panel/admin/AdminPanelTest.java @@ -54,6 +54,7 @@ class AdminPanelTest { void setUp() throws Exception { closeable = MockitoAnnotations.openMocks(this); ServerMock mbServer = MockBukkit.mock(); + PanelTestHelper.primeBukkitRegistry(); when(addon.getChallengesManager()).thenReturn(manager); PanelTestHelper.setupUserTranslations(user); diff --git a/src/test/java/world/bentobox/challenges/panel/admin/ListChallengesPanelTest.java b/src/test/java/world/bentobox/challenges/panel/admin/ListChallengesPanelTest.java index 36c2a55..2a184d8 100644 --- a/src/test/java/world/bentobox/challenges/panel/admin/ListChallengesPanelTest.java +++ b/src/test/java/world/bentobox/challenges/panel/admin/ListChallengesPanelTest.java @@ -47,6 +47,7 @@ class ListChallengesPanelTest { void setUp() { closeable = MockitoAnnotations.openMocks(this); ServerMock mbServer = MockBukkit.mock(); + PanelTestHelper.primeBukkitRegistry(); when(addon.getChallengesManager()).thenReturn(manager); PanelTestHelper.setupUserTranslations(user); diff --git a/src/test/java/world/bentobox/challenges/panel/user/GameModePanelTest.java b/src/test/java/world/bentobox/challenges/panel/user/GameModePanelTest.java index 038f09e..bffd0f0 100644 --- a/src/test/java/world/bentobox/challenges/panel/user/GameModePanelTest.java +++ b/src/test/java/world/bentobox/challenges/panel/user/GameModePanelTest.java @@ -58,6 +58,7 @@ class GameModePanelTest { void setUp() { closeable = MockitoAnnotations.openMocks(this); ServerMock mbServer = MockBukkit.mock(); + PanelTestHelper.primeBukkitRegistry(); when(addon.getChallengesManager()).thenReturn(manager); PanelTestHelper.setupUserTranslations(user); diff --git a/src/test/java/world/bentobox/challenges/panel/user/MultiplePanelTest.java b/src/test/java/world/bentobox/challenges/panel/user/MultiplePanelTest.java index 322b609..19ca940 100644 --- a/src/test/java/world/bentobox/challenges/panel/user/MultiplePanelTest.java +++ b/src/test/java/world/bentobox/challenges/panel/user/MultiplePanelTest.java @@ -53,6 +53,7 @@ class MultiplePanelTest { void setUp() { closeable = MockitoAnnotations.openMocks(this); ServerMock mbServer = MockBukkit.mock(); + PanelTestHelper.primeBukkitRegistry(); PanelTestHelper.setupUserTranslations(user); when(user.getWorld()).thenReturn(world);