diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java index f70a895a8..cf1bd6b92 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommand.java @@ -39,10 +39,11 @@ public class AdminPurgeCommand extends CompositeCommand implements Listener { setDescription("commands.admin.purge.description"); new AdminPurgeStopCommand(this); new AdminPurgeUnownedCommand(this); + new AdminPurgeProtectCommand(this); } @Override - public boolean execute(User user, String label, List args) { + public boolean canExecute(User user, String label, List args) { if (inPurge) { user.sendMessage("commands.admin.purge.purge-in-progress"); return false; @@ -52,6 +53,11 @@ public class AdminPurgeCommand extends CompositeCommand implements Listener { showHelp(this, user); return false; } + return true; + } + + @Override + public boolean execute(User user, String label, List args) { if (args.get(0).equalsIgnoreCase("confirm") && toBeConfirmed && this.user.equals(user)) { removeIslands(); return true; @@ -105,29 +111,14 @@ public class AdminPurgeCommand extends CompositeCommand implements Listener { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) void onIslandDeleted(IslandDeletedEvent e) { - if (inPurge && it.hasNext()) { - getIslands().getIslandById(it.next()).ifPresent(i -> { - getIslands().deleteIsland(i, true, null); - count++; - getPlugin().log(count + " islands purged"); - }); - } else { - user.sendMessage("commands.admin.purge.completed"); - inPurge = false; + if (inPurge) { + deleteIsland(); } } - Set getUnownedIslands() { - return getPlugin().getIslands().getIslands().stream() - .filter(i -> i.getWorld().equals(this.getWorld())) - .filter(i -> i.getOwner() == null) - .map(Island::getUniqueId) - .collect(Collectors.toSet()); - - } - Set getOldIslands(int days) { return getPlugin().getIslands().getIslands().stream() + .filter(i -> !i.getPurgeProtected()) .filter(i -> i.getWorld().equals(this.getWorld())) .filter(i -> i.getOwner() != null) .filter(i -> i.getMembers().size() == 1) @@ -153,14 +144,14 @@ public class AdminPurgeCommand extends CompositeCommand implements Listener { /** * @param user the user to set */ - public void setUser(User user) { + void setUser(User user) { this.user = user; } /** * @param islands the islands to set */ - public void setIslands(Set islands) { + void setIslands(Set islands) { this.islands = islands; } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeProtectCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeProtectCommand.java new file mode 100644 index 000000000..688b9a81d --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeProtectCommand.java @@ -0,0 +1,53 @@ +package world.bentobox.bentobox.api.commands.admin.purge; + +import java.util.List; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; + +public class AdminPurgeProtectCommand extends CompositeCommand { + + private Island island; + + public AdminPurgeProtectCommand(CompositeCommand parent) { + super(parent, "protect"); + } + + @Override + public void setup() { + setPermission("admin.purge"); + setOnlyPlayer(true); + setDescription("commands.admin.purge.protect.description"); + } + + @Override + public boolean canExecute(User user, String label, List args) { + if (!args.isEmpty()) { + // Show help + showHelp(this, user); + return false; + } + // Get island where the player is + if (!getIslands().getIslandAt(user.getLocation()).map(i -> { + island = i; + return true; + }).orElse(false)) { + user.sendMessage("commands.admin.purge.protect.move-to-island"); + return false; + } + return true; + } + + @Override + public boolean execute(User user, String label, List args) { + island.setPurgeProtected(!island.getPurgeProtected()); + if (island.getPurgeProtected()) { + user.sendMessage("commands.admin.purge.protect.protecting"); + } else { + user.sendMessage("commands.admin.purge.protect.unprotecting"); + } + return true; + + } +} diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeUnownedCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeUnownedCommand.java index d956c5315..ec2cf3ae7 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeUnownedCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeUnownedCommand.java @@ -2,10 +2,12 @@ package world.bentobox.bentobox.api.commands.admin.purge; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; public class AdminPurgeUnownedCommand extends ConfirmableCommand { @@ -33,7 +35,7 @@ public class AdminPurgeUnownedCommand extends ConfirmableCommand { user.sendMessage("commands.admin.purge.purge-in-progress"); return false; } - Set unowned = parentCommand.getUnownedIslands(); + Set unowned = getUnownedIslands(); user.sendMessage("commands.admin.purge.unowned.unowned-islands", TextVariables.NUMBER, String.valueOf(unowned.size())); if (!unowned.isEmpty()) { this.askConfirmation(user, () -> { @@ -44,4 +46,15 @@ public class AdminPurgeUnownedCommand extends ConfirmableCommand { } return true; } + + Set getUnownedIslands() { + return getPlugin().getIslands().getIslands().stream() + .filter(i -> !i.getPurgeProtected()) + .filter(i -> i.getWorld().equals(this.getWorld())) + .filter(i -> i.getOwner() == null) + .map(Island::getUniqueId) + .collect(Collectors.toSet()); + + } + } \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index 33b963881..94f8d13e9 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -911,6 +911,9 @@ public class Island implements DataObject { user.sendMessage("commands.admin.info.banned-players"); banned.forEach(u -> user.sendMessage("commands.admin.info.banned-format", TextVariables.NAME, plugin.getPlayers().getName(u))); } + if (purgeProtected) { + user.sendMessage("commands.admin.info.purge-protected"); + } return true; } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index b47928708..d7ebccd1e 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -66,7 +66,12 @@ commands: number-error: "&cArgument must be a number of days" confirm: "&dType [label] purge confirm to start purging" completed: "&aPurging stopped" - see-console-for-status: "Purge started. See console for status" + see-console-for-status: "Purge started. See console for status" + protect: + description: "Toggle island purge protection" + move-to-island: "&cMove to an island first!" + protecting: "&aPurge-protecting island" + unprotecting: "&aRemoving purge protection" stop: description: "Stop a purge in progress" stopping: "Stopping the purge" @@ -154,6 +159,7 @@ commands: island-coords: "Island coordinates: [xz1] to [xz2]" islands-in-trash: "&dPlayer has islands in trash." protection-range: "Protection range: [range]" + purge-protected: "Island is purge protected" max-protection-range: "Largest historical protection range: [range]" protection-coords: "Protection coordinates: [xz1] to [xz2]" is-spawn: "Island is a spawn island" diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommandTest.java new file mode 100644 index 000000000..b06ff34a2 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/purge/AdminPurgeCommandTest.java @@ -0,0 +1,342 @@ +/** + * + */ +package world.bentobox.bentobox.api.commands.admin.purge; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +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.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.api.addons.Addon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeletedEvent; +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.RanksManager; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Bukkit.class, BentoBox.class, User.class }) +public class AdminPurgeCommandTest { + + @Mock + private BentoBox plugin; + @Mock + private CompositeCommand ac; + @Mock + private User user; + @Mock + private IslandsManager im; + + private AdminPurgeCommand apc; + @Mock + private Addon addon; + @Mock + private Island island; + @Mock + private World world; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + when(ac.getWorld()).thenReturn(world); + + when(ac.getAddon()).thenReturn(addon); + when(ac.getTopLabel()).thenReturn("bsb"); + + // Island manager + when(plugin.getIslands()).thenReturn(im); + // No islands by default + when(im.getIslands()).thenReturn(Collections.emptyList()); + + // IWM + + IslandWorldManager iwm = mock(IslandWorldManager.class); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); + when(plugin.getIWM()).thenReturn(iwm); + + // Command + apc = new AdminPurgeCommand(ac); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#AdminPurgeCommand(CompositeCommand)}. + */ + @Test + public void testConstructor() { + verify(addon).registerListener(apc); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#setup()}. + */ + @Test + public void testSetup() { + assertEquals("admin.purge", apc.getPermission()); + assertFalse(apc.isOnlyPlayer()); + assertEquals("commands.admin.purge.parameters", apc.getParameters()); + assertEquals("commands.admin.purge.description", apc.getDescription()); + assertEquals(4, apc.getSubCommands().size()); + } + + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteUserStringListOfStringEmptyArgs() { + assertFalse(apc.canExecute(user, "protect", Collections.emptyList())); + verify(user).sendMessage("commands.help.header", + "[label]", + "BSkyBlock"); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteUserStringListOfStringWithArg() { + assertTrue(apc.canExecute(user, "protect", Collections.singletonList("23"))); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringNotNumber() { + assertFalse(apc.execute(user, "protect", Collections.singletonList("abc"))); + verify(user).sendMessage(eq("commands.admin.purge.number-error")); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringZero() { + assertFalse(apc.execute(user, "protect", Collections.singletonList("0"))); + verify(user).sendMessage(eq("commands.admin.purge.days-one-or-more")); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringNoIslands() { + assertTrue(apc.execute(user, "protect", Collections.singletonList("10"))); + verify(user).sendMessage(eq("commands.admin.purge.purgable-islands"), eq("[number]"), eq("0")); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringNoIslandsPurgeProtected() { + when(island.getPurgeProtected()).thenReturn(true); + when(im.getIslands()).thenReturn(Collections.singleton(island)); + assertTrue(apc.execute(user, "protect", Collections.singletonList("10"))); + verify(user).sendMessage(eq("commands.admin.purge.purgable-islands"), eq("[number]"), eq("0")); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringNoIslandsWrongWorld() { + when(island.getPurgeProtected()).thenReturn(false); + when(island.getWorld()).thenReturn(mock(World.class)); + when(im.getIslands()).thenReturn(Collections.singleton(island)); + assertTrue(apc.execute(user, "protect", Collections.singletonList("10"))); + verify(user).sendMessage(eq("commands.admin.purge.purgable-islands"), eq("[number]"), eq("0")); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringNoIslandsUnowned() { + when(island.getPurgeProtected()).thenReturn(false); + when(island.getWorld()).thenReturn(world); + when(island.getOwner()).thenReturn(null); + when(im.getIslands()).thenReturn(Collections.singleton(island)); + assertTrue(apc.execute(user, "protect", Collections.singletonList("10"))); + verify(user).sendMessage(eq("commands.admin.purge.purgable-islands"), eq("[number]"), eq("0")); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringNoIslandsTeamIsland() { + when(island.getPurgeProtected()).thenReturn(false); + when(island.getWorld()).thenReturn(world); + when(island.getOwner()).thenReturn(UUID.randomUUID()); + Map team = new HashMap<>(); + team.put(UUID.randomUUID(), RanksManager.OWNER_RANK); + team.put(UUID.randomUUID(), RanksManager.MEMBER_RANK); + when(island.getMembers()).thenReturn(team); + when(im.getIslands()).thenReturn(Collections.singleton(island)); + assertTrue(apc.execute(user, "protect", Collections.singletonList("10"))); + verify(user).sendMessage(eq("commands.admin.purge.purgable-islands"), eq("[number]"), eq("0")); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringNoIslandsRecentLogin() { + when(island.getPurgeProtected()).thenReturn(false); + when(island.getWorld()).thenReturn(world); + when(island.getOwner()).thenReturn(UUID.randomUUID()); + Map team = new HashMap<>(); + team.put(UUID.randomUUID(), RanksManager.OWNER_RANK); + when(island.getMembers()).thenReturn(team); + when(im.getIslands()).thenReturn(Collections.singleton(island)); + PowerMockito.mockStatic(Bukkit.class); + OfflinePlayer op = mock(OfflinePlayer.class); + when(op.getLastPlayed()).thenReturn(System.currentTimeMillis()); + when(Bukkit.getOfflinePlayer(any(UUID.class))).thenReturn(op); + assertTrue(apc.execute(user, "protect", Collections.singletonList("10"))); + verify(user).sendMessage(eq("commands.admin.purge.purgable-islands"), eq("[number]"), eq("0")); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringIslandsFound() { + when(island.getPurgeProtected()).thenReturn(false); + when(island.getWorld()).thenReturn(world); + when(island.getOwner()).thenReturn(UUID.randomUUID()); + Map team = new HashMap<>(); + team.put(UUID.randomUUID(), RanksManager.OWNER_RANK); + when(island.getMembers()).thenReturn(team); + when(im.getIslands()).thenReturn(Collections.singleton(island)); + PowerMockito.mockStatic(Bukkit.class); + OfflinePlayer op = mock(OfflinePlayer.class); + when(op.getLastPlayed()).thenReturn(0L); + when(Bukkit.getOfflinePlayer(any(UUID.class))).thenReturn(op); + assertFalse(apc.execute(user, "protect", Collections.singletonList("10"))); + verify(user).sendMessage(eq("commands.admin.purge.purgable-islands"), eq("[number]"), eq("1")); + verify(user).sendMessage(eq("commands.admin.purge.confirm"), eq("[label]"), eq("bsb")); + } + + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#removeIslands()}. + */ + @Test + public void testRemoveIslands() { + @NonNull + Optional opIsland = Optional.of(island); + when(im.getIslandById(any())).thenReturn(opIsland); + testExecuteUserStringListOfStringIslandsFound(); + assertTrue(apc.execute(user, "protect", Collections.singletonList("confirm"))); + verify(im).deleteIsland(eq(island), eq(true), eq(null)); + verify(plugin).log(eq("1 islands purged")); + verify(user).sendMessage(eq("commands.admin.purge.see-console-for-status")); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#onIslandDeleted(world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeletedEvent)}. + */ + @Test + public void testOnIslandDeletedNotInPurge() { + IslandDeletedEvent e = mock(IslandDeletedEvent.class); + apc.onIslandDeleted(e); + verify(user, Mockito.never()).sendMessage(any()); + verify(plugin, Mockito.never()).log(any()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#onIslandDeleted(world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeletedEvent)}. + */ + @Test + public void testOnIslandDeletedPurgeCompleted() { + testRemoveIslands(); + IslandDeletedEvent e = mock(IslandDeletedEvent.class); + apc.onIslandDeleted(e); + verify(user).sendMessage(eq("commands.admin.purge.completed")); + verify(plugin, Mockito.never()).log(""); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#isInPurge()}. + */ + @Test + public void testIsInPurge() { + assertFalse(apc.isInPurge()); + testRemoveIslands(); + assertTrue(apc.isInPurge()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#stop()}. + */ + @Test + public void testStop() { + testRemoveIslands(); + assertTrue(apc.isInPurge()); + apc.stop(); + assertFalse(apc.isInPurge()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand#setUser(world.bentobox.bentobox.api.user.User)}. + */ + @Test + public void testSetUser() { + apc.setUser(user); + apc.removeIslands(); + verify(user, Mockito.times(2)).sendMessage(any()); + } + + +}