From 24c68a0d955164f47bf1fd11649596e53dafcf7a Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 3 Mar 2024 16:07:49 -0800 Subject: [PATCH] Rewrite of admin setowner command #2309 --- .../admin/team/AdminTeamSetownerCommand.java | 28 ++- .../bentobox/managers/IslandsManager.java | 9 +- .../bentobox/managers/island/IslandCache.java | 2 + src/main/resources/locales/en-US.yml | 1 + .../team/AdminTeamSetownerCommandTest.java | 176 +++++++++++------- 5 files changed, 139 insertions(+), 77 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java index d0ef29b61..71f9a69d0 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java @@ -5,8 +5,11 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.events.island.IslandEvent; @@ -54,10 +57,6 @@ public class AdminTeamSetownerCommand extends ConfirmableCommand { user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); return false; } - if (!getIslands().inTeam(getWorld(), targetUUID)) { - user.sendMessage("general.errors.not-in-team"); - return false; - } // Check that user is on an island Optional opIsland = getIslands().getIslandAt(user.getLocation()); if (opIsland.isEmpty()) { @@ -83,7 +82,7 @@ public class AdminTeamSetownerCommand extends ConfirmableCommand { } - private void changeOwner(User user) { + protected void changeOwner(User user) { User target = User.getInstance(targetUUID); // Fire event so add-ons know // Call the setowner event @@ -101,6 +100,18 @@ public class AdminTeamSetownerCommand extends ConfirmableCommand { getIslands().setOwner(user, targetUUID, island, RanksManager.MEMBER_RANK); user.sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, target.getName()); + // Report if this made player have more islands than expected + // Get how many islands this player has + int num = this.getIslands().getNumberOfConcurrentIslands(targetUUID, getWorld()); + int max = target.getPermissionValue( + this.getIWM().getAddon(getWorld()).map(GameModeAddon::getPermissionPrefix).orElse("") + "island.number", + this.getIWM().getWorldSettings(getWorld()).getConcurrentIslands()); + if (num > max) { + // You cannot make an island + user.sendMessage("commands.admin.team.setowner.extra-islands", TextVariables.NUMBER, String.valueOf(num), + "[max]", String.valueOf(max)); + } + // Call the rank change event for the old island owner if (previousOwnerUUID != null) { // We need to call it AFTER the actual change. @@ -110,4 +121,11 @@ public class AdminTeamSetownerCommand extends ConfirmableCommand { } } + + @Override + public Optional> tabComplete(User user, String alias, List args) { + String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; + List options = Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(); + return Optional.of(Util.tabLimit(options, lastArg)); + } } diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 0ed4c2ab1..fff6cd23a 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1526,11 +1526,16 @@ public class IslandsManager { * @param rank rank to which to set old owner. */ public void setOwner(User user, UUID targetUUID, Island island, int rank) { - islandCache.setOwner(island, targetUUID); - // Set old owner as sub-owner on island. + // Demote the old owner + if (rank >= RanksManager.OWNER_RANK) { + plugin.logWarning("Setowner: previous owner's rank cannot be higher than SubOwner"); + rank = RanksManager.SUB_OWNER_RANK; + } if (rank > RanksManager.VISITOR_RANK && island.getOwner() != null) { island.setRank(island.getOwner(), rank); } + // Make the new owner + islandCache.setOwner(island, targetUUID); user.sendMessage("commands.island.team.setowner.name-is-the-owner", "[name]", plugin.getPlayers().getName(targetUUID)); diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java index 5e6896348..360e3b023 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; /** @@ -353,6 +354,7 @@ public class IslandCache { if (newOwnerUUID != null) { islandsByUUID.computeIfAbsent(newOwnerUUID, k -> new HashSet<>()).add(island); } + island.setRank(newOwnerUUID, RanksManager.OWNER_RANK); islandsByLocation.put(island.getCenter(), island); islandsById.put(island.getUniqueId(), island); } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index a10f99ed0..2bbbd78bb 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -147,6 +147,7 @@ commands: must-be-on-island: '&c You must be on the island to set the owner' confirmation: '&a Are you sure you want to set [name] to be the owner of the island at [xyz]?' success: '&b [name]&a is now the owner of this island.' + extra-islands: '&c Warning: this player now owns [number] islands. This is more than allowed by settings or perms: [max].' range: description: admin island range command invalid-value: diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommandTest.java index ade500c35..86ae24bac 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommandTest.java @@ -1,38 +1,45 @@ package world.bentobox.bentobox.api.commands.admin.team; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.Optional; import java.util.UUID; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.NonNull; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.stubbing.Answer; 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 world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.TestWorldSettings; import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.configuration.WorldSettings; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -40,6 +47,7 @@ import world.bentobox.bentobox.managers.CommandsManager; 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; @@ -53,16 +61,19 @@ public class AdminTeamSetownerCommandTest { @Mock private CompositeCommand ac; - private UUID uuid; + private UUID uuid = UUID.randomUUID(); @Mock private User user; @Mock private IslandsManager im; @Mock private PlayersManager pm; - private UUID notUUID; + private UUID notUUID = UUID.randomUUID(); @Mock private Island island; + private AdminTeamSetownerCommand itl; + @Mock + private @NonNull Location location; /** */ @@ -73,35 +84,57 @@ public class AdminTeamSetownerCommandTest { Whitebox.setInternalState(BentoBox.class, "instance", plugin); Util.setPlugin(plugin); + Settings settings = new Settings(); + // Settings + when(plugin.getSettings()).thenReturn(settings); + // Command manager CommandsManager cm = mock(CommandsManager.class); when(plugin.getCommandsManager()).thenReturn(cm); // Player Player p = mock(Player.class); + when(p.getUniqueId()).thenReturn(uuid); + when(p.getName()).thenReturn("tastybento"); + User.getInstance(p); // Sometimes use Mockito.withSettings().verboseLogging() when(user.isOp()).thenReturn(false); - uuid = UUID.randomUUID(); - notUUID = UUID.randomUUID(); - while (notUUID.equals(uuid)) { - notUUID = UUID.randomUUID(); - } when(user.getUniqueId()).thenReturn(uuid); when(user.getPlayer()).thenReturn(p); when(user.getName()).thenReturn("tastybento"); + when(user.getTranslation(anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + when(user.getTranslation(anyString(), anyString(), anyString(), anyString(), anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); User.setPlugin(plugin); + // Locales & Placeholders + LocalesManager lm = mock(LocalesManager.class); + when(lm.get(any(), any())).thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class)); + PlaceholdersManager phm = mock(PlaceholdersManager.class); + when(plugin.getPlaceholdersManager()).thenReturn(phm); + when(phm.replacePlaceholders(any(), any())) + .thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class)); + + when(plugin.getLocalesManager()).thenReturn(lm); + // Parent command has no aliases when(ac.getSubCommandAliases()).thenReturn(new HashMap<>()); // Island World Manager IslandWorldManager iwm = mock(IslandWorldManager.class); when(plugin.getIWM()).thenReturn(iwm); + @NonNull + WorldSettings worldSettings = new TestWorldSettings(); + when(iwm.getWorldSettings(any())).thenReturn(worldSettings); + // Location + when(location.toVector()).thenReturn(new Vector(1, 2, 3)); // Player has island to begin with when(im.hasIsland(any(), any(UUID.class))).thenReturn(true); when(im.hasIsland(any(), any(User.class))).thenReturn(true); when(island.getOwner()).thenReturn(uuid); + when(island.getCenter()).thenReturn(location); when(im.getPrimaryIsland(any(), any())).thenReturn(island); when(plugin.getIslands()).thenReturn(im); @@ -115,15 +148,13 @@ public class AdminTeamSetownerCommandTest { PowerMockito.mockStatic(Bukkit.class); when(Bukkit.getScheduler()).thenReturn(sch); - // Locales - LocalesManager lm = mock(LocalesManager.class); - when(lm.get(any(), any())).thenReturn("mock translation"); - when(plugin.getLocalesManager()).thenReturn(lm); - // Plugin Manager PluginManager pim = mock(PluginManager.class); when(Bukkit.getPluginManager()).thenReturn(pim); + // DUT + itl = new AdminTeamSetownerCommand(ac); + } @After @@ -133,56 +164,45 @@ public class AdminTeamSetownerCommandTest { } /** - * Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamSetownerCommand#canExecute(User, String, List)}. */ @Test public void testExecuteNoTarget() { - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - assertFalse(itl.execute(user, itl.getLabel(), new ArrayList<>())); + assertFalse(itl.canExecute(user, itl.getLabel(), new ArrayList<>())); // Show help + verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "commands.help.console"); } /** - * Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamSetownerCommand#setup()} + */ + @Test + public void testSetup() { + assertEquals("commands.admin.team.setowner.description", itl.getDescription()); + assertEquals("commands.admin.team.setowner.parameters", itl.getParameters()); + assertTrue(itl.isOnlyPlayer()); + assertEquals("mod.team.setowner", itl.getPermission()); + } + + /** + * Test method for {@link AdminTeamSetownerCommand#canExecute(User, String, List)}. */ @Test public void testExecuteUnknownPlayer() { - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - String[] name = { "tastybento" }; - when(pm.getUUID(any())).thenReturn(null); - assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name))); - verify(user).sendMessage("general.errors.unknown-player", "[name]", name[0]); + assertFalse(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + verify(user).sendMessage("general.errors.unknown-player", "[name]", "tastybento"); } /** - * Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}. - */ - @Test - public void testExecutePlayerNotInTeam() { - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - String[] name = { "tastybento" }; - when(pm.getUUID(any())).thenReturn(notUUID); - // when(im.getMembers(any(), any())).thenReturn(new HashSet<>()); - assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name))); - verify(user).sendMessage(eq("general.errors.not-in-team")); - } - - /** - * Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamSetownerCommand#canExecute(User, String, List)}. */ @Test public void testExecuteMakeOwnerAlreadyOwner() { - when(im.inTeam(any(), any())).thenReturn(true); - Island is = mock(Island.class); - when(im.getIsland(any(), any(UUID.class))).thenReturn(is); - String[] name = {"tastybento"}; - when(pm.getUUID(any())).thenReturn(notUUID); - when(pm.getName(any())).thenReturn(name[0]); - when(island.getOwner()).thenReturn(notUUID); - - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name))); - verify(user).sendMessage("commands.admin.team.setowner.already-owner", TextVariables.NAME, name[0]); + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getOwner()).thenReturn(uuid); + when(Util.getUUID("tastybento")).thenReturn(uuid); + assertFalse(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + verify(user).sendMessage("commands.admin.team.setowner.already-owner", TextVariables.NAME, "tastybento"); } /** @@ -190,28 +210,44 @@ public class AdminTeamSetownerCommandTest { */ @Test public void testExecuteSuccess() { - // Player is a team member, not an owner - when(im.hasIsland(any(), any(UUID.class))).thenReturn(false); - when(im.hasIsland(any(), any(User.class))).thenReturn(false); - when(im.inTeam(any(), any())).thenReturn(true); - Island is = mock(Island.class); - when(im.getIsland(any(), any(UUID.class))).thenReturn(is); - String[] name = {"tastybento"}; - when(pm.getUUID(any())).thenReturn(notUUID); - when(pm.getName(any())).thenReturn(name[0]); - // Owner - //when(im.getOwner(any(), eq(notUUID))).thenReturn(uuid); - when(pm.getName(eq(uuid))).thenReturn("owner"); - // Members - Set members = new HashSet<>(); - members.add(uuid); - members.add(notUUID); - //when(im.getMembers(any(), any())).thenReturn(members); + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getOwner()).thenReturn(notUUID); + when(Util.getUUID("tastybento")).thenReturn(uuid); - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - assertTrue(itl.execute(user, itl.getLabel(), Arrays.asList(name))); + assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + assertTrue(itl.execute(user, itl.getLabel(), List.of("tastybento"))); // Add other verifications - verify(im).setOwner(any(), eq(user), eq(notUUID)); - verify(user).sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, name[0]); + verify(user).getTranslation("commands.admin.team.setowner.confirmation", TextVariables.NAME, "tastybento", + TextVariables.XYZ, "1,2,3"); + } + + /** + * Test method for {@link AdminTeamSetownerCommand#changeOwner(User)} + */ + @Test + public void testChangeOwner() { + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getOwner()).thenReturn(notUUID); + when(Util.getUUID("tastybento")).thenReturn(uuid); + + assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + itl.changeOwner(user); + // Add other verifications + verify(user).sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, "tastybento"); + } + + /** + * Test method for {@link AdminTeamSetownerCommand#changeOwner(User)} + */ + @Test + public void testChangeOwnerNoOwner() { + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getOwner()).thenReturn(null); + when(Util.getUUID("tastybento")).thenReturn(uuid); + + assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + itl.changeOwner(user); + // Add other verifications + verify(user).sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, "tastybento"); } }