diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java index 7a7bf4031..2b137baf6 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java @@ -34,6 +34,11 @@ public class AdminBlueprintSaveCommand extends ConfirmableCommand { BlueprintClipboard clipboard = parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard()); String fileName = args.get(0).toLowerCase(Locale.ENGLISH); if (clipboard.isFull()) { + // Check if blueprint had bedrock + if (clipboard.getBlueprint().getBedrock() == null) { + user.sendMessage("commands.admin.blueprint.bedrock-required"); + return false; + } // Check if file exists File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX); if (newFile.exists()) { diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java index 650bce0a5..7378ace35 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java @@ -14,6 +14,9 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; +import org.bukkit.Material; +import org.bukkit.util.Vector; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -21,6 +24,7 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.Blueprint; import world.bentobox.bentobox.blueprints.BlueprintClipboard; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock; import world.bentobox.bentobox.database.json.BentoboxTypeAdapterFactory; /** @@ -101,8 +105,17 @@ public class BlueprintClipboardManager { Blueprint bp; try (FileReader fr = new FileReader(file)) { bp = gson.fromJson(fr, Blueprint.class); + } catch (Exception e) { + plugin.logError("Blueprint has JSON error: " + zipFile.getName()); + throw new IOException("Blueprint has JSON error: " + zipFile.getName()); } Files.delete(file.toPath()); + // Bedrock check and set + if (bp.getBedrock() == null) { + bp.setBedrock(new Vector(bp.getxSize() / 2, bp.getySize() / 2, bp.getzSize() / 2)); + bp.getBlocks().put(bp.getBedrock(), new BlueprintBlock(Material.BEDROCK.createBlockData().getAsString())); + plugin.logWarning("Blueprint " + fileName + " had no bedrock block in it so one was added automatically in the center. You should check it."); + } return bp; } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index f43735325..a9ed6a50a 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -193,6 +193,7 @@ commands: blueprint: parameters: "<load/copy/paste/pos1/pos2/save>" description: "manipulate blueprints" + bedrock-required: "&cAt least one bedrock block must be in a blueprint!" copy-first: "&cCopy first!" file-exists: "&cFile already exists, overwrite?" no-such-file: "&cNo such file!" diff --git a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java new file mode 100644 index 000000000..8f34e57ac --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java @@ -0,0 +1,385 @@ +/** + * + */ +package world.bentobox.bentobox.managers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +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.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Comparator; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.configuration.file.YamlConfiguration; +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 world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.blueprints.Blueprint; +import world.bentobox.bentobox.blueprints.BlueprintClipboard; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest( {Bukkit.class, BentoBox.class} ) +public class BlueprintClipboardManagerTest { + + @Mock + private BentoBox plugin; + @Mock + private BlueprintClipboard clipboard; + + private File blueprintFolder; + + private String json = "{\n" + + " \"name\": \"blueprint\",\n" + + " \"attached\": {},\n" + + " \"entities\": {},\n" + + " \"blocks\": [\n" + + " [\n" + + " [3.0, -5.0, 8.0], {\n" + + " \"blockData\": \"minecraft:stone\"\n" + + " }\n" + + " ],\n" + + " [\n" + + " [6.0, -13.0, -20.0], {\n" + + " \"blockData\": \"minecraft:diorite\"\n" + + " }\n" + + " ]\n" + + " ],\n" + + " \"xSize\": 10,\n" + + " \"ySize\": 10,\n" + + " \"zSize\": 10,\n" + + " \"bedrock\": [-2.0, -16.0, -1.0]\n" + + "}"; + + private String jsonNoBedrock = "{\n" + + " \"name\": \"blueprint\",\n" + + " \"attached\": {},\n" + + " \"entities\": {},\n" + + " \"blocks\": [\n" + + " [\n" + + " [3.0, -5.0, 8.0], {\n" + + " \"blockData\": \"minecraft:stone\"\n" + + " }\n" + + " ],\n" + + " [\n" + + " [6.0, -13.0, -20.0], {\n" + + " \"blockData\": \"minecraft:diorite\"\n" + + " }\n" + + " ]\n" + + " ],\n" + + " \"xSize\": 10,\n" + + " \"ySize\": 10,\n" + + " \"zSize\": 10\n" + + "}"; + + private void zip(File targetFile) throws IOException { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(targetFile.getAbsolutePath() + BlueprintsManager.BLUEPRINT_SUFFIX))) { + zipOutputStream.putNextEntry(new ZipEntry(targetFile.getName())); + try (FileInputStream inputStream = new FileInputStream(targetFile)) { + final byte[] buffer = new byte[1024]; + int length; + while((length = inputStream.read(buffer)) >= 0) { + zipOutputStream.write(buffer, 0, length); + } + } + try { + Files.delete(targetFile.toPath()); + } catch (Exception e) { + plugin.logError(e.getMessage()); + } + } + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + blueprintFolder = new File("blueprints"); + PowerMockito.mockStatic(Bukkit.class); + BlockData blockData = mock(BlockData.class); + when(Bukkit.createBlockData(Mockito.any(Material.class))).thenReturn(blockData); + when(blockData.getAsString()).thenReturn("test123"); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + if (blueprintFolder.exists()) { + // Clean up file system + Files.walk(blueprintFolder.toPath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#BlueprintClipboardManager(world.bentobox.bentobox.BentoBox, java.io.File)}. + */ + @Test + public void testBlueprintClipboardManagerBentoBoxFile() { + new BlueprintClipboardManager(plugin, blueprintFolder); + assertTrue(blueprintFolder.exists()); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#BlueprintClipboardManager(world.bentobox.bentobox.BentoBox, java.io.File, world.bentobox.bentobox.blueprints.BlueprintClipboard)}. + */ + @Test + public void testBlueprintClipboardManagerBentoBoxFileBlueprintClipboard() { + new BlueprintClipboardManager(plugin, blueprintFolder, clipboard); + assertTrue(blueprintFolder.exists()); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#getClipboard()}. + */ + @Test + public void testGetClipboard() { + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder, clipboard); + assertEquals(clipboard, bcm.getClipboard()); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#loadBlueprint(java.lang.String)}. + * @throws IOException + */ + @Test + public void testLoadBlueprintNoSuchFile() { + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + try { + bcm.loadBlueprint("test"); + } catch (Exception e) { + assertTrue(e instanceof IOException); + } finally { + verify(plugin).logError("Could not load blueprint file - does not exist : test.blu"); + } + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#loadBlueprint(java.lang.String)}. + * @throws IOException + */ + @Test + public void testLoadBlueprintNoFileInZip() throws IOException { + blueprintFolder.mkdirs(); + // Make a blueprint file + YamlConfiguration config = new YamlConfiguration(); + config.set("hello", "this is a test"); + File configFile = new File(blueprintFolder, "blueprint.blu"); + config.save(configFile); + assertTrue(configFile.exists()); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + try { + bcm.loadBlueprint("blueprint"); + } catch (Exception e) { + assertTrue(e instanceof IOException); + } finally { + verify(plugin).logError("Could not load blueprint file - does not exist : blueprint"); + } + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#loadBlueprint(java.lang.String)}. + * @throws IOException + */ + @Test + public void testLoadBlueprintFileInZipJSONError() throws IOException { + blueprintFolder.mkdirs(); + // Make a blueprint file + YamlConfiguration config = new YamlConfiguration(); + config.set("hello", "this is a test"); + File configFile = new File(blueprintFolder, "blueprint"); + config.save(configFile); + // Zip it + zip(configFile); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + try { + bcm.loadBlueprint("blueprint"); + } catch (Exception e) { + assertTrue(e instanceof IOException); + } finally { + verify(plugin).logError("Blueprint has JSON error: blueprint.blu"); + } + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#loadBlueprint(java.lang.String)}. + * @throws IOException + */ + @Test + public void testLoadBlueprintFileInZipNoBedrock() throws IOException { + blueprintFolder.mkdirs(); + // Make a blueprint file + File configFile = new File(blueprintFolder, "blueprint"); + byte[] bytes = jsonNoBedrock.getBytes(StandardCharsets.UTF_8); + Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); + // Zip it + zip(configFile); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + Blueprint bp = bcm.loadBlueprint("blueprint"); + verify(plugin).logWarning("Blueprint blueprint had no bedrock block in it so one was added automatically in the center. You should check it."); + // Verify bedrock was placed in the center of the blueprint + assertEquals(5, bp.getBedrock().getBlockX()); + assertEquals(5, bp.getBedrock().getBlockY()); + assertEquals(5, bp.getBedrock().getBlockZ()); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#loadBlueprint(java.lang.String)}. + * @throws IOException + */ + @Test + public void testLoadBlueprintFileInZip() throws IOException { + blueprintFolder.mkdirs(); + // Make a blueprint file + File configFile = new File(blueprintFolder, "blueprint"); + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); + Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); + // Zip it + zip(configFile); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + Blueprint bp = bcm.loadBlueprint("blueprint"); + assertEquals(-2, bp.getBedrock().getBlockX()); + assertEquals(-16, bp.getBedrock().getBlockY()); + assertEquals(-1, bp.getBedrock().getBlockZ()); + assertTrue(bp.getAttached().isEmpty()); + assertTrue(bp.getEntities().isEmpty()); + assertEquals(2, bp.getBlocks().size()); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#load(java.lang.String)}. + * @throws IOException + */ + @Test + public void testLoadString() throws IOException { + blueprintFolder.mkdirs(); + // Make a blueprint file + File configFile = new File(blueprintFolder, "blueprint"); + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); + Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); + // Zip it + zip(configFile); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + bcm.load("blueprint"); + Blueprint bp = bcm.getClipboard().getBlueprint(); + assertEquals(-2, bp.getBedrock().getBlockX()); + assertEquals(-16, bp.getBedrock().getBlockY()); + assertEquals(-1, bp.getBedrock().getBlockZ()); + assertTrue(bp.getAttached().isEmpty()); + assertTrue(bp.getEntities().isEmpty()); + assertEquals(2, bp.getBlocks().size()); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#load(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * @throws IOException + */ + @Test + public void testLoadUserString() throws IOException { + blueprintFolder.mkdirs(); + // Make a blueprint file + File configFile = new File(blueprintFolder, "blueprint"); + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); + Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); + // Zip it + zip(configFile); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + User user = mock(User.class); + assertTrue(bcm.load(user, "blueprint")); + verify(user).sendMessage("general.success"); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#load(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * @throws IOException + */ + @Test + public void testLoadUserStringFail() throws IOException { + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + User user = mock(User.class); + assertFalse(bcm.load(user, "blueprint")); + verify(user).sendMessage("commands.admin.blueprint.could-not-load"); + verify(plugin).logError("Could not load blueprint file - does not exist : blueprint.blu"); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * @throws IOException + */ + @Test + public void testSave() throws IOException { + // Load a blueprint, then save it + blueprintFolder.mkdirs(); + // Make a blueprint file + File configFile = new File(blueprintFolder, "blueprint"); + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); + Files.write(configFile.toPath(), bytes, StandardOpenOption.CREATE); + // Zip it + zip(configFile); + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + bcm.load("blueprint"); + User user = mock(User.class); + assertTrue(bcm.save(user, "test1234")); + File bp = new File(blueprintFolder, "test1234.blu"); + assertTrue(bp.exists()); + verify(user).sendMessage("general.success"); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#saveBlueprint(world.bentobox.bentobox.blueprints.Blueprint)}. + */ + @Test + public void testSaveBlueprintNoName() { + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + Blueprint blueprint = mock(Blueprint.class); + when(blueprint.getName()).thenReturn(""); + assertFalse(bcm.saveBlueprint(blueprint)); + verify(plugin).logError("Blueprint name was empty - could not save it"); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#saveBlueprint(world.bentobox.bentobox.blueprints.Blueprint)}. + */ + @Test + public void testSaveBlueprintSuccess() { + BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); + Blueprint blueprint = new Blueprint(); + blueprint.setName("test123"); + assertTrue(bcm.saveBlueprint(blueprint)); + File bp = new File(blueprintFolder, "test123.blu"); + assertTrue(bp.exists()); + } + +}