diff --git a/src/test/java/world/bentobox/controlpanel/CommonTestSetup.java b/src/test/java/world/bentobox/controlpanel/CommonTestSetup.java
new file mode 100644
index 0000000..e80d7a0
--- /dev/null
+++ b/src/test/java/world/bentobox/controlpanel/CommonTestSetup.java
@@ -0,0 +1,274 @@
+package world.bentobox.controlpanel;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Player.Spigot;
+import org.bukkit.event.entity.EntityExplodeEvent;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.inventory.ItemFactory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.bukkit.metadata.FixedMetadataValue;
+import org.bukkit.metadata.MetadataValue;
+import org.bukkit.plugin.PluginManager;
+import org.bukkit.scheduler.BukkitScheduler;
+import org.bukkit.util.Vector;
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.mockbukkit.mockbukkit.MockBukkit;
+import org.mockbukkit.mockbukkit.ServerMock;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import com.google.common.collect.ImmutableSet;
+
+import net.md_5.bungee.api.chat.TextComponent;
+import world.bentobox.bentobox.BentoBox;
+import world.bentobox.bentobox.api.configuration.WorldSettings;
+import world.bentobox.bentobox.api.user.Notifier;
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.database.objects.Players;
+import world.bentobox.bentobox.managers.BlueprintsManager;
+import world.bentobox.bentobox.managers.FlagsManager;
+import world.bentobox.bentobox.managers.HooksManager;
+import world.bentobox.bentobox.managers.IslandWorldManager;
+import world.bentobox.bentobox.managers.IslandsManager;
+import world.bentobox.bentobox.managers.LocalesManager;
+import world.bentobox.bentobox.managers.PlaceholdersManager;
+import world.bentobox.bentobox.managers.PlayersManager;
+import world.bentobox.bentobox.util.Util;
+
+/**
+ * Common items for testing. Don't forget to use super.setUp()!
+ *
+ * Sets up BentoBox plugin, pluginManager and ItemFactory.
+ * Location, world, playersManager and player.
+ * IWM, Addon and WorldSettings. IslandManager with one
+ * island with protection and nothing allowed by default.
+ * Owner of island is player with same UUID.
+ * Locales, placeholders.
+ */
+public abstract class CommonTestSetup {
+
+ protected UUID uuid = UUID.randomUUID();
+
+ @Mock
+ protected Player mockPlayer;
+ @Mock
+ protected PluginManager pim;
+ @Mock
+ protected ItemFactory itemFactory;
+ @Mock
+ protected Location location;
+ @Mock
+ protected World world;
+ @Mock
+ protected IslandWorldManager iwm;
+ @Mock
+ protected IslandsManager im;
+ @Mock
+ protected Island island;
+ @Mock
+ protected BentoBox plugin;
+ @Mock
+ protected PlayerInventory inv;
+ @Mock
+ protected Notifier notifier;
+ @Mock
+ protected FlagsManager fm;
+ @Mock
+ protected Spigot spigot;
+ @Mock
+ protected HooksManager hooksManager;
+ @Mock
+ protected BlueprintsManager bm;
+
+ protected ServerMock server;
+
+ protected MockedStatic mockedBukkit;
+ protected MockedStatic mockedUtil;
+
+ protected AutoCloseable closeable;
+
+ @Mock
+ protected BukkitScheduler sch;
+ @Mock
+ protected LocalesManager lm;
+
+ @Mock
+ protected PlaceholdersManager phm;
+
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ closeable = MockitoAnnotations.openMocks(this);
+ server = MockBukkit.mock();
+ // Set up plugin
+ WhiteBox.setInternalState(BentoBox.class, "instance", plugin);
+
+ // Bukkit static mock
+ mockedBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_DEEP_STUBS);
+ mockedBukkit.when(Bukkit::getMinecraftVersion).thenReturn("1.21.10");
+ mockedBukkit.when(Bukkit::getBukkitVersion).thenReturn("");
+ mockedBukkit.when(Bukkit::getPluginManager).thenReturn(pim);
+ mockedBukkit.when(Bukkit::getItemFactory).thenReturn(itemFactory);
+ mockedBukkit.when(Bukkit::getServer).thenReturn(server);
+ // Location
+ when(location.getWorld()).thenReturn(world);
+ when(location.getBlockX()).thenReturn(0);
+ when(location.getBlockY()).thenReturn(0);
+ when(location.getBlockZ()).thenReturn(0);
+ when(location.toVector()).thenReturn(new Vector(0, 0, 0));
+ when(location.clone()).thenReturn(location);
+
+ // Players Manager and meta data
+ PlayersManager pm = mock(PlayersManager.class);
+ when(plugin.getPlayers()).thenReturn(pm);
+ Players players = mock(Players.class);
+ when(players.getMetaData()).thenReturn(Optional.empty());
+ when(pm.getPlayer(any(UUID.class))).thenReturn(players);
+
+ // Player
+ when(mockPlayer.getUniqueId()).thenReturn(uuid);
+ when(mockPlayer.getLocation()).thenReturn(location);
+ when(mockPlayer.getWorld()).thenReturn(world);
+ when(mockPlayer.getName()).thenReturn("tastybento");
+ when(mockPlayer.getInventory()).thenReturn(inv);
+ when(mockPlayer.spigot()).thenReturn(spigot);
+ when(mockPlayer.getType()).thenReturn(EntityType.PLAYER);
+
+ User.setPlugin(plugin);
+ User.clearUsers();
+ User.getInstance(mockPlayer);
+
+ // IWM
+ when(plugin.getIWM()).thenReturn(iwm);
+ when(iwm.inWorld(any(Location.class))).thenReturn(true);
+ when(iwm.inWorld(any(World.class))).thenReturn(true);
+ when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock");
+ when(iwm.getAddon(any())).thenReturn(Optional.empty());
+
+ // World Settings
+ WorldSettings worldSet = new TestWorldSettings();
+ when(iwm.getWorldSettings(any())).thenReturn(worldSet);
+
+ // Island Manager
+ when(plugin.getIslands()).thenReturn(im);
+ Optional optionalIsland = Optional.of(island);
+ when(im.getProtectedIslandAt(any())).thenReturn(optionalIsland);
+
+ // Island - nothing is allowed by default
+ when(island.isAllowed(any())).thenReturn(false);
+ when(island.isAllowed(any(User.class), any())).thenReturn(false);
+ when(island.getOwner()).thenReturn(uuid);
+ when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid));
+
+ // Enable reporting from Flags class
+ MetadataValue mdv = new FixedMetadataValue(plugin, "_why_debug");
+ when(mockPlayer.getMetadata(anyString())).thenReturn(Collections.singletonList(mdv));
+
+ // Locales & Placeholders
+ when(lm.get(any(), any())).thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class));
+ when(plugin.getPlaceholdersManager()).thenReturn(phm);
+ when(phm.replacePlaceholders(any(), any())).thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class));
+ when(plugin.getLocalesManager()).thenReturn(lm);
+ // Notifier
+ when(plugin.getNotifier()).thenReturn(notifier);
+
+ // Fake players
+ world.bentobox.bentobox.Settings settings = new world.bentobox.bentobox.Settings();
+ when(plugin.getSettings()).thenReturn(settings);
+
+ // Util
+ mockedUtil = Mockito.mockStatic(Util.class, Mockito.CALLS_REAL_METHODS);
+ mockedUtil.when(() -> Util.getWorld(any())).thenReturn(mock(World.class));
+ Util.setPlugin(plugin);
+
+ mockedUtil.when(() -> Util.findFirstMatchingEnum(any(), any())).thenCallRealMethod();
+
+ // Server & Scheduler
+ mockedBukkit.when(Bukkit::getScheduler).thenReturn(sch);
+
+ // Hooks
+ when(hooksManager.getHook(anyString())).thenReturn(Optional.empty());
+ when(plugin.getHooks()).thenReturn(hooksManager);
+
+ // Blueprints Manager
+ when(plugin.getBlueprintsManager()).thenReturn(bm);
+ }
+
+ @AfterEach
+ public void tearDown() throws Exception {
+ mockedBukkit.closeOnDemand();
+ mockedUtil.closeOnDemand();
+ closeable.close();
+ MockBukkit.unmock();
+ User.clearUsers();
+ Mockito.framework().clearInlineMocks();
+ deleteAll(new File("database"));
+ deleteAll(new File("database_backup"));
+ }
+
+ protected static void deleteAll(File file) throws IOException {
+ if (file.exists()) {
+ Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ }
+ }
+
+ public void checkSpigotMessage(String expectedMessage) {
+ checkSpigotMessage(expectedMessage, 1);
+ }
+
+ @SuppressWarnings("deprecation")
+ public void checkSpigotMessage(String expectedMessage, int expectedOccurrences) {
+ ArgumentCaptor captor = ArgumentCaptor.forClass(TextComponent.class);
+ verify(spigot, atLeast(0)).sendMessage(captor.capture());
+
+ List capturedMessages = captor.getAllValues();
+ long actualOccurrences = capturedMessages.stream().map(component -> component.toLegacyText())
+ .filter(messageText -> messageText.contains(expectedMessage))
+ .count();
+
+ assertEquals(expectedOccurrences,
+ actualOccurrences, "Expected message occurrence mismatch: " + expectedMessage);
+ }
+
+ public EntityExplodeEvent getExplodeEvent(Entity entity, Location l, List list) {
+ return new EntityExplodeEvent(entity, l, list, 0, null);
+ }
+
+ public PlayerDeathEvent getPlayerDeathEvent(Player player, List drops, int droppedExp, int newExp,
+ int newTotalExp, int newLevel, @Nullable String deathMessage) {
+ return new PlayerDeathEvent(player, null, drops, droppedExp, newExp,
+ newTotalExp, newLevel, deathMessage);
+ }
+}
diff --git a/src/test/java/world/bentobox/controlpanel/ControlPanelAddonTest.java b/src/test/java/world/bentobox/controlpanel/ControlPanelAddonTest.java
new file mode 100644
index 0000000..1e80600
--- /dev/null
+++ b/src/test/java/world/bentobox/controlpanel/ControlPanelAddonTest.java
@@ -0,0 +1,220 @@
+package world.bentobox.controlpanel;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import world.bentobox.bentobox.api.addons.Addon.State;
+import world.bentobox.bentobox.api.addons.AddonDescription;
+import world.bentobox.bentobox.api.addons.GameModeAddon;
+import world.bentobox.bentobox.api.commands.CompositeCommand;
+import world.bentobox.bentobox.api.configuration.Config;
+import world.bentobox.bentobox.database.AbstractDatabaseHandler;
+import world.bentobox.bentobox.database.Database;
+import world.bentobox.bentobox.database.DatabaseSetup;
+import world.bentobox.bentobox.managers.AddonsManager;
+import world.bentobox.controlpanel.config.Settings;
+import world.bentobox.controlpanel.managers.ControlPanelManager;
+
+class ControlPanelAddonTest extends CommonTestSetup {
+
+ private ControlPanelAddon addon;
+
+ @Mock
+ private AddonsManager addonsManager;
+
+ private MockedStatic mockedDbSetup;
+
+ @SuppressWarnings("unchecked")
+ @BeforeEach
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ addon = new ControlPanelAddon();
+
+ // Set up data folder
+ File dataFolder = new File("addons/ControlPanel");
+ dataFolder.mkdirs();
+ addon.setDataFolder(dataFolder);
+
+ // Create JAR file with config.yml
+ File jFile = new File("addon.jar");
+ try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jFile))) {
+ // Add config.yml
+ Path configSrc = Paths.get("src/main/resources/config.yml");
+ if (Files.exists(configSrc)) {
+ Path configDest = Paths.get("config.yml");
+ Files.copy(configSrc, configDest, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+ addToJar(configDest, jos);
+ Files.deleteIfExists(configDest);
+ }
+ // Add controlPanelTemplate.yml
+ Path templateSrc = Paths.get("src/main/resources/controlPanelTemplate.yml");
+ if (Files.exists(templateSrc)) {
+ Path templateDest = Paths.get("controlPanelTemplate.yml");
+ Files.copy(templateSrc, templateDest, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+ addToJar(templateDest, jos);
+ Files.deleteIfExists(templateDest);
+ }
+ }
+ addon.setFile(jFile);
+
+ AddonDescription desc = new AddonDescription.Builder("main", "ControlPanel", "1.16.0")
+ .description("test").authors("tastybento").build();
+ addon.setDescription(desc);
+
+ when(plugin.getAddonsManager()).thenReturn(addonsManager);
+ when(addonsManager.getGameModeAddons()).thenReturn(Collections.emptyList());
+ when(plugin.isEnabled()).thenReturn(true);
+ when(plugin.getLogger()).thenReturn(java.util.logging.Logger.getAnonymousLogger());
+ when(plugin.getFlagsManager()).thenReturn(fm);
+ when(fm.getFlags()).thenReturn(Collections.emptyList());
+
+ // Mock DatabaseSetup for ControlPanelManager creation
+ AbstractDatabaseHandler