diff --git a/build.gradle b/build.gradle index c71b9a63..aa4b5740 100644 --- a/build.gradle +++ b/build.gradle @@ -136,7 +136,7 @@ dependencies { // Tests testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21' - testImplementation 'com.github.seeseemelk:MockBukkit-v1.21:3.133.0' + testImplementation 'org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.3.1' testImplementation('com.googlecode.json-simple:json-simple:1.1.1') { exclude group: 'junit', module: 'junit' } diff --git a/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java b/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java index b46ec662..a94e2f29 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java @@ -185,17 +185,12 @@ public class WorldManager { return worldActionResult(CreateFailureReason.WORLD_EXIST_LOADED, options.worldName()); } else if (getWorld(options.worldName()).isDefined()) { return worldActionResult(CreateFailureReason.WORLD_EXIST_UNLOADED, options.worldName()); - } else if (hasWorldFolder(options.worldName())) { + } else if (worldNameChecker.hasWorldFolder(options.worldName())) { return worldActionResult(CreateFailureReason.WORLD_EXIST_FOLDER, options.worldName()); } return worldActionResult(options); } - private boolean hasWorldFolder(String worldName) { - File worldFolder = new File(Bukkit.getWorldContainer(), worldName); - return worldFolder.exists(); - } - private Attempt createValidatedWorld( CreateWorldOptions options) { String parsedGenerator = parseGenerator(options.worldName(), options.generator()); @@ -530,9 +525,6 @@ public class WorldManager { Logging.severe("Invalid world name: " + newWorldName); return worldActionResult(CloneFailureReason.INVALID_WORLDNAME, newWorldName); } - if (worldNameChecker.isValidWorldFolder(newWorldName)) { - return worldActionResult(CloneFailureReason.WORLD_EXIST_FOLDER, newWorldName); - } if (isLoadedWorld(newWorldName)) { Logging.severe("World already loaded when attempting to clone: " + newWorldName); return worldActionResult(CloneFailureReason.WORLD_EXIST_LOADED, newWorldName); @@ -541,6 +533,9 @@ public class WorldManager { Logging.severe("World already exist unloaded: " + newWorldName); return worldActionResult(CloneFailureReason.WORLD_EXIST_UNLOADED, newWorldName); } + if (worldNameChecker.hasWorldFolder(newWorldName)) { + return worldActionResult(CloneFailureReason.WORLD_EXIST_FOLDER, newWorldName); + } return worldActionResult(options); } diff --git a/src/main/java/org/mvplugins/multiverse/core/world/WorldNameChecker.java b/src/main/java/org/mvplugins/multiverse/core/world/WorldNameChecker.java index 65d86fd4..184ee035 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/WorldNameChecker.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/WorldNameChecker.java @@ -4,6 +4,7 @@ import java.io.File; import java.util.Set; import java.util.regex.Pattern; +import io.vavr.control.Option; import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -45,17 +46,32 @@ public class WorldNameChecker { */ @NotNull public NameStatus checkName(@Nullable String worldName) { - if (BLACKLIST_NAMES.contains(worldName)) { - return NameStatus.BLACKLISTED; - } - if (worldName == null || !WORLD_NAME_PATTERN.matcher(worldName).matches()) { - return NameStatus.INVALID_CHARS; - } - return NameStatus.VALID; + return Option.of(worldName).map(name -> { + if (name.isEmpty()) { + return NameStatus.EMPTY; + } + if (BLACKLIST_NAMES.contains(name)) { + return NameStatus.BLACKLISTED; + } + if (!WORLD_NAME_PATTERN.matcher(name).matches()) { + return NameStatus.INVALID_CHARS; + } + return NameStatus.VALID; + }).getOrElse(NameStatus.EMPTY); } /** - * Checks if a world name has a valid world folder. + * Check if a world name has a world folder directory. It may not contain valid world data. + * + * @param worldName The world name to check on. + * @return True if the folder exists, else false. + */ + public boolean hasWorldFolder(@Nullable String worldName) { + return checkFolder(worldName) != FolderStatus.DOES_NOT_EXIST; + } + + /** + * Checks if a world name has a valid world folder with basic world data. * * @param worldName The world name to check on. * @return True if check result is valid, else false. @@ -65,7 +81,7 @@ public class WorldNameChecker { } /** - * Checks if a world folder is valid. + * Checks if a world folder is valid with basic world data. * * @param worldFolder The world folder to check on. * @return True if check result is valid, else false. @@ -132,6 +148,11 @@ public class WorldNameChecker { */ INVALID_CHARS, + /** + * Name string that is null or length 0. + */ + EMPTY, + /** * Name not valid as it is deemed blacklisted. */ diff --git a/src/test/java/org/mvplugins/multiverse/core/TestWithMockBukkit.kt b/src/test/java/org/mvplugins/multiverse/core/TestWithMockBukkit.kt index 6521180c..975d7680 100644 --- a/src/test/java/org/mvplugins/multiverse/core/TestWithMockBukkit.kt +++ b/src/test/java/org/mvplugins/multiverse/core/TestWithMockBukkit.kt @@ -1,27 +1,31 @@ package org.mvplugins.multiverse.core -import be.seeseemelk.mockbukkit.MockBukkit -import be.seeseemelk.mockbukkit.ServerMock +import com.dumptruckman.minecraft.util.Logging +import org.mockbukkit.mockbukkit.MockBukkit import org.mvplugins.multiverse.core.inject.PluginServiceLocator +import org.mvplugins.multiverse.core.mock.MVServerMock import org.mvplugins.multiverse.core.utils.TestingMode import kotlin.test.AfterTest import kotlin.test.BeforeTest +import kotlin.test.assertNotNull /** * Basic abstract test class that sets up MockBukkit and MultiverseCore. */ abstract class TestWithMockBukkit { - protected lateinit var server: ServerMock + protected lateinit var server: MVServerMock protected lateinit var multiverseCore: MultiverseCore protected lateinit var serviceLocator : PluginServiceLocator @BeforeTest fun setUpMockBukkit() { TestingMode.enable() - server = MockBukkit.mock() + server = MockBukkit.mock(MVServerMock()) multiverseCore = MockBukkit.load(MultiverseCore::class.java) + Logging.setDebugLevel(3) serviceLocator = multiverseCore.serviceLocator + assertNotNull(server.commandMap) } @AfterTest diff --git a/src/test/java/org/mvplugins/multiverse/core/commands/VersionCommandTest.kt b/src/test/java/org/mvplugins/multiverse/core/commands/VersionCommandTest.kt index 1a0fd939..e642e883 100644 --- a/src/test/java/org/mvplugins/multiverse/core/commands/VersionCommandTest.kt +++ b/src/test/java/org/mvplugins/multiverse/core/commands/VersionCommandTest.kt @@ -1,8 +1,8 @@ package org.mvplugins.multiverse.core.commands -import be.seeseemelk.mockbukkit.entity.PlayerMock import org.bukkit.Bukkit import org.bukkit.ChatColor +import org.mockbukkit.mockbukkit.entity.PlayerMock import org.mvplugins.multiverse.core.TestWithMockBukkit import kotlin.test.BeforeTest import kotlin.test.Test diff --git a/src/test/java/org/mvplugins/multiverse/core/mock/MVServerMock.java b/src/test/java/org/mvplugins/multiverse/core/mock/MVServerMock.java new file mode 100644 index 00000000..1ed0cb32 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/core/mock/MVServerMock.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.core.mock; + +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.jetbrains.annotations.NotNull; +import org.mockbukkit.mockbukkit.ServerMock; +import org.mockbukkit.mockbukkit.command.CommandMapMock; +import org.mockbukkit.mockbukkit.world.WorldMock; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class MVServerMock extends ServerMock { + + private final File worldContainer; + + public MVServerMock() throws IOException { + super(); + this.worldContainer = Files.createTempDirectory("world-container").toFile(); + this.worldContainer.deleteOnExit(); + System.out.println("Created test world folder: " + this.worldContainer.getAbsolutePath()); + } + + // This is required for acf reflection to work + @Override + public @NotNull CommandMapMock getCommandMap() { + return super.getCommandMap(); + } + + @Override + public @NotNull File getWorldContainer() { + return this.worldContainer; + } + + @Override + public World createWorld(@NotNull WorldCreator creator) { + WorldMock world = new MVWorldMock(creator); + world.getWorldFolder().mkdirs(); + createFile(new File(world.getWorldFolder(), "uid.dat")); + createFile(new File(world.getWorldFolder(), "level.dat")); + addWorld(world); + return world; + } + + private void createFile(File file) { + try { + file.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/mvplugins/multiverse/core/mock/MVWorldMock.java b/src/test/java/org/mvplugins/multiverse/core/mock/MVWorldMock.java new file mode 100644 index 00000000..2d2adfa1 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/core/mock/MVWorldMock.java @@ -0,0 +1,30 @@ +package org.mvplugins.multiverse.core.mock; + +import org.bukkit.WorldCreator; +import org.jetbrains.annotations.NotNull; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.world.WorldMock; + +import java.io.File; + +public class MVWorldMock extends WorldMock { + + private final File worldFolder; + private final boolean generateStructures; + + public MVWorldMock(@NotNull WorldCreator creator) { + super(creator); + this.worldFolder = new File(MockBukkit.getMock().getWorldContainer(), getName()); + this.generateStructures = creator.generateStructures(); + } + + @Override + public @NotNull File getWorldFolder() { + return this.worldFolder; + } + + @Override + public boolean canGenerateStructures() { + return this.generateStructures; + } +} diff --git a/src/test/java/org/mvplugins/multiverse/core/world/WorldManagerTest.kt b/src/test/java/org/mvplugins/multiverse/core/world/WorldManagerTest.kt index b735f383..858c543f 100644 --- a/src/test/java/org/mvplugins/multiverse/core/world/WorldManagerTest.kt +++ b/src/test/java/org/mvplugins/multiverse/core/world/WorldManagerTest.kt @@ -1,47 +1,209 @@ package org.mvplugins.multiverse.core.world +import org.bukkit.Bukkit import org.bukkit.World import org.bukkit.WorldType import org.mvplugins.multiverse.core.TestWithMockBukkit +import org.mvplugins.multiverse.core.world.options.CloneWorldOptions import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import org.mvplugins.multiverse.core.world.options.RegenWorldOptions +import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions +import org.mvplugins.multiverse.core.world.reasons.CloneFailureReason +import org.mvplugins.multiverse.core.world.reasons.CreateFailureReason +import org.mvplugins.multiverse.core.world.reasons.LoadFailureReason +import java.io.File import kotlin.test.* class WorldManagerTest : TestWithMockBukkit() { private lateinit var worldManager: WorldManager private lateinit var world: LoadedMultiverseWorld + private lateinit var world2: LoadedMultiverseWorld @BeforeTest fun setUp() { worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { throw IllegalStateException("WorldManager is not available as a service") } - worldManager.createWorld(CreateWorldOptions.worldName("world")) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) world = worldManager.getLoadedWorld("world").get() assertNotNull(world) + + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess) + world2 = worldManager.getLoadedWorld("world2").get() + assertNotNull(world2) } @Test - fun `Creates a new world`() { - worldManager.createWorld(CreateWorldOptions.worldName("world_nether") + fun `Create world with custom options`() { + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world_nether") .environment(World.Environment.NETHER) .generateStructures(false) .seed(1234L) - .useSpawnAdjust(true) + .useSpawnAdjust(false) .worldType(WorldType.FLAT) - ) + ).isSuccess) - val world = worldManager.getLoadedWorld("world_nether").get() + val getWorld = worldManager.getLoadedWorld("world_nether") + assertTrue(getWorld.isDefined) + val world = getWorld.get() assertNotNull(world) assertEquals("world_nether", world.name) assertEquals(World.Environment.NETHER, world.environment) - assertEquals("", world.generator) + assertFalse(world.canGenerateStructures().get()) assertEquals(1234L, world.seed) + assertFalse(world.adjustSpawn) + assertEquals(WorldType.FLAT, world.worldType.get()) + assertEquals("", world.generator) + } + + @Test + fun `Create world failed - invalid worldname`() { + assertEquals( + CreateFailureReason.INVALID_WORLDNAME, + worldManager.createWorld(CreateWorldOptions.worldName("*!@^&#*(")).failureReason + ) + } + + @Test + fun `Create world failed - world exists and loaded`() { + assertEquals( + CreateFailureReason.WORLD_EXIST_LOADED, + worldManager.createWorld(CreateWorldOptions.worldName("world")).failureReason + ) + } + + @Test + fun `Create world failed - world exists but unloaded`() { + assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world)).isSuccess) + assertEquals( + CreateFailureReason.WORLD_EXIST_UNLOADED, + worldManager.createWorld(CreateWorldOptions.worldName("world")).failureReason + ) + } + + @Test + fun `Create world failed - world folder exists`() { + File(Bukkit.getWorldContainer(), "worldfolder").mkdir() + assertEquals( + CreateFailureReason.WORLD_EXIST_FOLDER, + worldManager.createWorld(CreateWorldOptions.worldName("worldfolder")).failureReason + ) + } + + @Test + fun `Remove world`() { + assertTrue(worldManager.removeWorld(world).isSuccess) + assertFalse(worldManager.getWorld("world").isDefined) + assertFalse(worldManager.getLoadedWorld("world").isDefined) + assertFalse(worldManager.getUnloadedWorld("world").isDefined) } @Test fun `Delete world`() { - worldManager.deleteWorld(world) + assertTrue(worldManager.deleteWorld(world).isSuccess) assertFalse(worldManager.getLoadedWorld("world").isDefined) } + + @Test + fun `Unload and load world`() { + assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2).saveBukkitWorld(true)).isSuccess) + assertFalse(world2.isLoaded) + assertFalse(world2.bukkitWorld.isDefined) + assertFalse(worldManager.getLoadedWorld("world2").isDefined) + assertTrue(worldManager.getWorld("world2").isDefined) + assertTrue(worldManager.getUnloadedWorld("world2").isDefined) + + assertTrue(worldManager.loadWorld("world2").isSuccess) + assertTrue(world2.isLoaded) + // todo: think what to do when a world is re-loaded with a different uid + // assertTrue(world2.bukkitWorld.isDefined) + assertTrue(worldManager.getLoadedWorld("world2").isDefined) + assertFalse(worldManager.getUnloadedWorld("world2").isDefined) + } + + @Test + fun `Load world failed - non-existent world`() { + assertEquals( + LoadFailureReason.WORLD_NON_EXISTENT, + worldManager.loadWorld("ghost").failureReason + ) + } + + @Test + fun `Load world failed - world folder exists but not imported`() { + File(Bukkit.getWorldContainer(), "worldfolder").mkdir() + File(Bukkit.getWorldContainer(), "worldfolder/level.dat").createNewFile() + assertEquals( + LoadFailureReason.WORLD_EXIST_FOLDER, + worldManager.loadWorld("worldfolder").failureReason + ) + } + + @Test + fun `Regen world`() { + assertTrue(worldManager.regenWorld(RegenWorldOptions + .world(world2) + .seed(4321L) + ).isSuccess) + + val getWorld = worldManager.getLoadedWorld("world2") + assertTrue(getWorld.isDefined) + val world = getWorld.get() + assertNotNull(world) + assertEquals(4321L, world.seed) + } + + @Test + fun `Clone world`() { + assertTrue(worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "cloneworld")).isSuccess) + val getWorld = worldManager.getLoadedWorld("cloneworld") + assertTrue(getWorld.isDefined) + val world = getWorld.get() + assertNotNull(world) + assertEquals("cloneworld", world.name) + } + + @Test + fun `Clone world failed - invalid world name`() { + assertEquals( + CloneFailureReason.INVALID_WORLDNAME, + worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "HU(@*!#")).failureReason + ) + } + + @Test + fun `Clone world failed - target world exists and loaded`() { + assertEquals( + CloneFailureReason.WORLD_EXIST_LOADED, + worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "world2")).failureReason + ) + } + + @Test + fun `Clone world failed - target world exists but unloaded`() { + assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess) + assertEquals( + CloneFailureReason.WORLD_EXIST_UNLOADED, + worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "world2")).failureReason + ) + } + + @Test + fun `Clone world failed - target world folder exists`() { + File(Bukkit.getWorldContainer(), "worldfolder").mkdir() + assertEquals( + CloneFailureReason.WORLD_EXIST_FOLDER, + worldManager.cloneWorld(CloneWorldOptions.fromTo(world, "worldfolder")).failureReason + ) + } + + @Test + fun `Get potential worlds`() { + File(Bukkit.getWorldContainer(), "newworld1").mkdir() + File(Bukkit.getWorldContainer(), "newworld1/level.dat").createNewFile() + File(Bukkit.getWorldContainer(), "newworld2").mkdir() + File(Bukkit.getWorldContainer(), "newworld2/level.dat").createNewFile() + assertEquals(listOf("newworld1", "newworld2"), worldManager.getPotentialWorlds()) + } } diff --git a/src/test/java/org/mvplugins/multiverse/core/world/WorldNameCheckerTest.kt b/src/test/java/org/mvplugins/multiverse/core/world/WorldNameCheckerTest.kt new file mode 100644 index 00000000..17e5c94c --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/core/world/WorldNameCheckerTest.kt @@ -0,0 +1,61 @@ +package org.mvplugins.multiverse.core.world + +import org.bukkit.Bukkit +import org.mvplugins.multiverse.core.TestWithMockBukkit +import java.io.File +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.* + +class WorldNameCheckerTest : TestWithMockBukkit() { + + lateinit var worldNameChecker: WorldNameChecker + + @BeforeTest + fun setUp() { + worldNameChecker = serviceLocator.getActiveService(WorldNameChecker::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldNameChecker is not available as a service") } + } + + @Test + fun `Valid world name`() { + assertEquals(WorldNameChecker.NameStatus.VALID, worldNameChecker.checkName("test")) + } + + @Test + fun `Invalid characters in world name`() { + assertEquals(WorldNameChecker.NameStatus.INVALID_CHARS, worldNameChecker.checkName("test!")) + } + + @Test + fun `Empty world name`() { + assertEquals(WorldNameChecker.NameStatus.EMPTY, worldNameChecker.checkName(null)) + assertEquals(WorldNameChecker.NameStatus.EMPTY, worldNameChecker.checkName("")) + } + + @Test + fun `Blacklisted world name`() { + assertEquals(WorldNameChecker.NameStatus.BLACKLISTED, worldNameChecker.checkName("logs")) + assertEquals(WorldNameChecker.NameStatus.BLACKLISTED, worldNameChecker.checkName("plugins")) + } + + @Test + fun `Valid world folder`() { + File(Bukkit.getWorldContainer(), "test").mkdir() + File(Bukkit.getWorldContainer(), "test/level.dat").createNewFile() + assertEquals(WorldNameChecker.FolderStatus.VALID, worldNameChecker.checkFolder("test")) + } + + @Test + fun `Not a valid world folder`() { + File(Bukkit.getWorldContainer(), "test").mkdir() + File(Bukkit.getWorldContainer(), "test/random.txt").createNewFile() + assertEquals(WorldNameChecker.FolderStatus.NOT_A_WORLD, worldNameChecker.checkFolder("test")) + } + + @Test + fun `World folder does not exist`() { + File(Bukkit.getWorldContainer(), "test").mkdir() + assertEquals(WorldNameChecker.FolderStatus.DOES_NOT_EXIST, worldNameChecker.checkFolder("test2")) + } +}