From 80e10630155d75227e49359d692175ce00aa712a Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 30 Sep 2024 17:50:22 -0700 Subject: [PATCH] Add AdminResetHome command #2522 Fixed bug with tab complete for MaxHomes and fixed tests. --- .../commands/admin/AdminMaxHomesCommand.java | 7 +- .../commands/admin/AdminResetHomeCommand.java | 160 ++++++++ .../commands/admin/DefaultAdminCommand.java | 2 + src/main/resources/locales/en-US.yml | 7 +- .../admin/AdminMaxHomesCommandTest.java | 5 +- .../admin/AdminResetHomeCommandTest.java | 354 ++++++++++++++++++ 6 files changed, 531 insertions(+), 4 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/api/commands/admin/AdminResetHomeCommand.java create mode 100644 src/test/java/world/bentobox/bentobox/api/commands/admin/AdminResetHomeCommandTest.java diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminMaxHomesCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminMaxHomesCommand.java index 8f38bf4d4..c2e2dec5a 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminMaxHomesCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminMaxHomesCommand.java @@ -140,7 +140,12 @@ public class AdminMaxHomesCommand extends ConfirmableCommand { return Optional.of(Util.getOnlinePlayerList(user)); } if (args.size() > 3) { - return Optional.of(Util.tabLimit(new ArrayList<>(getNameIslandMap(user).keySet()), lastArg)); + // Work out who is in arg 2 + UUID targetUUID = getPlayers().getUUID(args.get(1)); + if (targetUUID != null) { + User target = User.getInstance(targetUUID); + return Optional.of(Util.tabLimit(new ArrayList<>(getNameIslandMap(target).keySet()), lastArg)); + } } return Optional.of(List.of("1")); diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminResetHomeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminResetHomeCommand.java new file mode 100644 index 000000000..135e33c48 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminResetHomeCommand.java @@ -0,0 +1,160 @@ +package world.bentobox.bentobox.api.commands.admin; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; + + +/** + * This command resets players island name. + * @author BONNe + */ +public class AdminResetHomeCommand extends CompositeCommand +{ + Map islands = new HashMap<>(); + + /** + * Default constructor. + * @param command Parent command. + */ + public AdminResetHomeCommand(CompositeCommand command) + { + super(command, "resethome"); + } + + + /** + * {@inheritDoc} + */ + @Override + public void setup() + { + this.setPermission("mod.resethome"); + this.setDescription("commands.admin.resethome.description"); + this.setParametersHelp("commands.admin.resethome.parameters"); + } + + + /** + * @param user the {@link User} who is executing this command. + * @param label the label which has been used to execute this command. + * It can be {@link CompositeCommand#getLabel()} or an alias. + * @param args the command arguments. + * @return {@code true} if name can be reset, {@code false} otherwise. + */ + @Override + public boolean canExecute(User user, String label, List args) + { + islands.clear(); + if (args.isEmpty()) { + this.showHelp(this, user); + return false; + } + // First arg must be a valid player name + UUID targetUUID = getPlayers().getUUID(args.get(0)); + if (targetUUID == null) { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return false; + } + // Get islands + islands = this.getNameIslandMap(User.getInstance(targetUUID)); + if (islands.isEmpty()) { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } + + // Second optional arg must be the name of the island + if (args.size() == 1) { + return true; + } + + // A specific island is mentioned. Parse which one it is and remove the others + final String name = String.join(" ", args.subList(1, args.size())); // Join all the args from here with spaces + + islands.keySet().removeIf(n -> !name.equalsIgnoreCase(n)); + + if (islands.isEmpty()) { + // Failed name check - there are either + user.sendMessage("commands.admin.maxhomes.errors.unknown-island", TextVariables.NAME, name); + return false; + } + + return true; + } + + + /** + * @param user the {@link User} who is executing this command. + * @param label the label which has been used to execute this command. + * It can be {@link CompositeCommand#getLabel()} or an alias. + * @param args the command arguments. + * @return {@code true} + */ + @Override + public boolean execute(User user, String label, List args) + { + if (islands.isEmpty()) { + // Sanity check + return false; + } + islands.forEach((name, island) -> { + island.getHomes().keySet().removeIf(String::isEmpty); // Remove the default home + user.sendMessage("commands.admin.resethome.cleared", TextVariables.NAME, name); + }); + + user.sendMessage("general.success"); + return true; + } + + + @Override + public Optional> tabComplete(User user, String alias, List args) { + String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; + if (args.size() == 2) { + // Suggest player names + return Optional.of(Util.getOnlinePlayerList(user)); + } + if (args.size() > 2) { + // Work out who is in arg 2 + UUID targetUUID = getPlayers().getUUID(args.get(0)); + if (targetUUID != null) { + User target = User.getInstance(targetUUID); + return Optional.of(Util.tabLimit(new ArrayList<>(getNameIslandMap(target).keySet()), lastArg)); + } + } + return Optional.empty(); + + } + + Map getNameIslandMap(User user) { + Map islandMap = new HashMap<>(); + int index = 0; + System.out.println("Getting for " + user.getName()); + for (Island island : getIslands().getIslands(getWorld(), user.getUniqueId())) { + System.out.println("Island - " + island); + index++; + if (island.getName() != null && !island.getName().isBlank()) { + // Name has been set + islandMap.put(island.getName(), island); + } else { + // Name has not been set + String text = user.getTranslation("protection.flags.ENTER_EXIT_MESSAGES.island", TextVariables.NAME, + user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName()) + " " + index; + islandMap.put(text, island); + } + } + + return islandMap; + + } + +} diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java index 00ad796f8..4b3274a2c 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java @@ -104,6 +104,8 @@ public abstract class DefaultAdminCommand extends CompositeCommand { new AdminResetNameCommand(this); // Max homes new AdminMaxHomesCommand(this); + // Reset Home + new AdminResetHomeCommand(this); } /** diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index bd5529aee..7f4c07915 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -61,11 +61,14 @@ commands: description: admin command maxhomes: description: change the number of homes allowed on this island or player's island - parameters: + parameters: [island name] max-homes-set: '&a [name] - Set island max homes to [number]' errors: unknown-island: &c Unknown island! [name] - + resethome: + description: Reset the player's home to default + parameters: [island name] + cleared: '&b Home reset. [name]' resets: description: edit player reset values set: diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminMaxHomesCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminMaxHomesCommandTest.java index 903952864..36cf06113 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminMaxHomesCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminMaxHomesCommandTest.java @@ -4,6 +4,7 @@ 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.doReturn; import static org.mockito.Mockito.mock; @@ -135,6 +136,8 @@ public class AdminMaxHomesCommandTest { // Has team when(im.inTeam(any(), eq(uuid))).thenReturn(true); + // Players + when(pm.getUUID(anyString())).thenReturn(uuid); when(plugin.getPlayers()).thenReturn(pm); // Server & Scheduler @@ -457,7 +460,7 @@ public class AdminMaxHomesCommandTest { Map nameIslandMap = new HashMap<>(); nameIslandMap.put("IslandOne", mock(Island.class)); nameIslandMap.put("IslandTwo", mock(Island.class)); - doReturn(nameIslandMap).when(instance).getNameIslandMap(user); + doReturn(nameIslandMap).when(instance).getNameIslandMap(any()); // Create the list of island names List islandNames = new ArrayList<>(nameIslandMap.keySet()); diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminResetHomeCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminResetHomeCommandTest.java new file mode 100644 index 000000000..d5a733d97 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminResetHomeCommandTest.java @@ -0,0 +1,354 @@ +package world.bentobox.bentobox.api.commands.admin; + +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.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +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.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; +import org.eclipse.jdt.annotation.Nullable; +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.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.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +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.PlayersManager; +import world.bentobox.bentobox.util.Util; + +/** + * @author tastybento + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class }) +public class AdminResetHomeCommandTest { + + @Mock + private CompositeCommand ac; + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private PlayersManager pm; + private UUID notUUID; + private UUID uuid; + @Mock + private World world; + @Mock + private @Nullable Island island; + private AdminResetHomeCommand instance; + private String label; + private ArrayList args = new ArrayList<>(); + + @Before + public void setUp() throws Exception { + PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); + + // Set up plugin + BentoBox plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + + // Util + Util.setPlugin(plugin); + + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + // Settings + Settings s = mock(Settings.class); + when(s.getResetCooldown()).thenReturn(0); + when(plugin.getSettings()).thenReturn(s); + + // Player + Player p = mock(Player.class); + // 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"); + User.setPlugin(plugin); + + // Parent command has no aliases + when(ac.getSubCommandAliases()).thenReturn(new HashMap<>()); + when(ac.getTopLabel()).thenReturn("admin"); + when(ac.getWorld()).thenReturn(world); + + // Island World Manager + IslandWorldManager iwm = mock(IslandWorldManager.class); + when(plugin.getIWM()).thenReturn(iwm); + + // 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(im.isOwner(any(),any())).thenReturn(true); + // when(im.getOwner(any(),any())).thenReturn(uuid); + when(im.getIsland(world, user)).thenReturn(island); + when(im.getIslands(world, notUUID)).thenReturn(List.of(island)); + when(plugin.getIslands()).thenReturn(im); + + // Island + when(island.getOwner()).thenReturn(uuid); + when(island.hasTeam()).thenReturn(true); + + // Has team + when(im.inTeam(any(), eq(uuid))).thenReturn(true); + + // Players + when(pm.getUUID(anyString())).thenReturn(uuid); + when(plugin.getPlayers()).thenReturn(pm); + + // Server & Scheduler + BukkitScheduler sch = mock(BukkitScheduler.class); + when(Bukkit.getScheduler()).thenReturn(sch); + BukkitTask task = mock(BukkitTask.class); + when(sch.runTaskLater(any(), any(Runnable.class), any(Long.class))).thenReturn(task); + + // Locales + LocalesManager lm = mock(LocalesManager.class); + when(lm.get(any(), any())).thenReturn("mock translation"); + when(plugin.getLocalesManager()).thenReturn(lm); + + instance = spy(new AdminResetHomeCommand(ac)); + label = "island"; + } + + @After + public void tearDown() { + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.AdminMaxHomesCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testArgsIsEmpty() { + // Arrange: args is already empty + + // Act + boolean result = instance.canExecute(user, label, args); + + // Assert + assertFalse(result); + // Verify that showHelp was called + verify(instance).showHelp(instance, user); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.AdminMaxHomesCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testArgsSizeGreaterThan1_InvalidPlayer() { + // Arrange + args.add("UnknownPlayer"); + + when(pm.getUUID("UnknownPlayer")).thenReturn(null); + + // Act + boolean result = instance.canExecute(user, label, args); + + // Assert + assertFalse(result); + verify(user).sendMessage("general.errors.unknown-player", TextVariables.NAME, "UnknownPlayer"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.AdminMaxHomesCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testArgsSizeGreaterThan2_UnknownIsland() { + // Arrange + args.add("ValidPlayer"); + args.add("UnknownIsland"); + + UUID playerUUID = UUID.randomUUID(); + when(pm.getUUID("ValidPlayer")).thenReturn(playerUUID); + + User targetUser = mock(User.class); + // Mock static method User.getInstance(UUID) + // Assuming use of Mockito with inline mocking or PowerMockito + PowerMockito.mockStatic(User.class); + when(User.getInstance(playerUUID)).thenReturn(targetUser); + + Map islandsMap = new HashMap<>(); + islandsMap.put("Island1", mock(Island.class)); + doReturn(islandsMap).when(instance).getNameIslandMap(targetUser); + + // Act + boolean result = instance.canExecute(user, label, args); + + // Assert + assertFalse(result); + verify(user).sendMessage("commands.admin.maxhomes.errors.unknown-island", TextVariables.NAME, "UnknownIsland"); + } + + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.AdminMaxHomesCommand#setup()}. + */ + @Test + public void testSetup() { + assertEquals("mod.resethome", instance.getPermission()); + assertFalse(instance.isOnlyPlayer()); + assertEquals("commands.admin.resethome.parameters", instance.getParameters()); + assertEquals("commands.admin.resethome.description", instance.getDescription()); + + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.AdminMaxHomesCommand#tabComplete(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testTabComplete_ArgsSize2_ReturnsPlayerNames() { + // Arrange + args.add("someArg"); // args.size() == 1 + args.add(""); // args.size() == 2 + + // Mock Util.getOnlinePlayerList(user) + List onlinePlayers = Arrays.asList("PlayerOne", "PlayerTwo"); + PowerMockito.mockStatic(Util.class); + when(Util.getOnlinePlayerList(user)).thenReturn(onlinePlayers); + + // Act + Optional> result = instance.tabComplete(user, label, args); + + // Assert + assertTrue(result.isPresent()); + assertEquals(onlinePlayers, result.get()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.AdminMaxHomesCommand#tabComplete(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testTabComplete_ArgsSizeGreaterThan2_ReturnsIslandNames() { + // Arrange + args.add("someArg"); + args.add("anotherArg"); + args.add(""); // args.size() == 3 (>2) + String lastArg = args.get(args.size() - 1); + + Map nameIslandMap = new HashMap<>(); + nameIslandMap.put("IslandOne", mock(Island.class)); + nameIslandMap.put("IslandTwo", mock(Island.class)); + doReturn(nameIslandMap).when(instance).getNameIslandMap(any()); + + // Create the list of island names + List islandNames = new ArrayList<>(nameIslandMap.keySet()); + + // Mock Util.tabLimit() + List limitedIslandNames = Arrays.asList("IslandOne", "IslandTwo"); + PowerMockito.mockStatic(Util.class); + when(Util.tabLimit(islandNames, lastArg)).thenReturn(limitedIslandNames); + + // Act + Optional> result = instance.tabComplete(user, label, args); + + // Assert + assertTrue(result.isPresent()); + assertEquals(limitedIslandNames, result.get()); + + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.AdminMaxHomesCommand#tabComplete(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteWithEmptyIslands_ShouldReturnFalse() { + // Arrange + instance.islands = new HashMap<>(); // Empty islands map + + // Act + boolean result = instance.execute(user, label, args); + + // Assert + assertFalse(result); + } + + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.AdminMaxHomesCommand#tabComplete(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteSuccessful_SingleIsland() { + // Arrange + Island island = mock(Island.class); + Map islandsMap = new HashMap<>(); + islandsMap.put("TestIsland", island); + instance.islands = islandsMap; + + // Act + boolean result = instance.execute(user, label, args); + + // Assert + assertTrue(result); + verify(island).getHomes(); + verify(user).sendMessage("commands.admin.resethome.cleared", TextVariables.NAME, "TestIsland"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.AdminMaxHomesCommand#tabComplete(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteSuccessful_MultipleIslands() { + // Arrange + Island island1 = mock(Island.class); + Island island2 = mock(Island.class); + Map islandsMap = new HashMap<>(); + islandsMap.put("IslandOne", island1); + islandsMap.put("IslandTwo", island2); + instance.islands = islandsMap; + + // Act + boolean result = instance.execute(user, label, args); + + // Assert + assertTrue(result); + verify(island1).getHomes(); + verify(island2).getHomes(); + verify(user).sendMessage("commands.admin.resethome.cleared", TextVariables.NAME, "IslandOne"); + verify(user).sendMessage("commands.admin.resethome.cleared", TextVariables.NAME, "IslandTwo"); + } + +}