diff --git a/src/main/java/gov/hhs/aspr/ms/util/resourcehelper/ResourceError.java b/src/main/java/gov/hhs/aspr/ms/util/resourcehelper/ResourceError.java index 686d170..f81caa8 100644 --- a/src/main/java/gov/hhs/aspr/ms/util/resourcehelper/ResourceError.java +++ b/src/main/java/gov/hhs/aspr/ms/util/resourcehelper/ResourceError.java @@ -3,7 +3,9 @@ import gov.hhs.aspr.ms.util.errors.ContractError; public enum ResourceError implements ContractError { - UNKNOWN_FILE("Provided file does not exist"); + UNKNOWN_FILE("Provided file does not exist"), + FILE_PATH_IS_DIRECTORY("The provided file path points to a directory and not a file"), + DIRECTORY_PATH_IS_FILE("The provided directory path points to a file and not a directory"); private final String description; diff --git a/src/main/java/gov/hhs/aspr/ms/util/resourcehelper/ResourceHelper.java b/src/main/java/gov/hhs/aspr/ms/util/resourcehelper/ResourceHelper.java index 1757f7b..c8d5edb 100644 --- a/src/main/java/gov/hhs/aspr/ms/util/resourcehelper/ResourceHelper.java +++ b/src/main/java/gov/hhs/aspr/ms/util/resourcehelper/ResourceHelper.java @@ -5,78 +5,262 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.Files; import java.nio.file.Path; import gov.hhs.aspr.ms.util.errors.ContractException; +/** + * ResourceHelper is a class designed to solve the issue that sometimes arises + * when running UnitTests and using files from the resources directory within + * the src/test folder. + *

+ * The issue is that the relative path to the resource folder is dependant on + * what folder you are executing the test from. This can vary from IDE to IDE + * and even from where you run the maven command if using maven to run the unit + * tests. + *

+ * This solves the issues by obtaining an absolute reference to the resource + * directory by using the class loader and an empty resource. + *

+ * In addition to the above, this class also provides convience methods to + * validate file paths and directory paths, and create directories and files. + */ public class ResourceHelper { - private ResourceHelper() { - } - + /** + * Given a class ref, uses the class loader from the classref and an empty + * resource name to obtain a URI and then creates and returns a Path from that + * URI. This path will be an absolute path and not a relative path. Because it + * uses the classloader, it no longer matters from where this method gets called + * with respect to the java command used to call it. + *

+ * This solves the problem of unit tests that use files from the + * src/test/resources sometimes failing because of the directory from which the + * test was executed. + * + * @throws RuntimeException if the url provided by the classloader cannot be + * converted to a valid URI. Note that the + * RuntimeException wraps the thrown + * {@link URISyntaxException} + */ public static Path getResourceDir(Class classRef) { return Path.of(getURI(classRef.getClassLoader().getResource(""))); } - protected static URI getURI(URL url) { - try { - return url.toURI(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); + /** + * Given a path, creates the directory. This internally calls + * dirPath.toFile().mkdirs(). + */ + public static Path createDirectory(Path dirPath) { + if (dirPath.toFile().exists()) { + return dirPath; } - } - public static Path makeOutputDir(Path dirPath) { dirPath.toFile().mkdirs(); return dirPath; } - public static Path makeOutputDir(Path basepath, String subDir) { - Path dirPath = basepath.resolve(subDir); + /** + * Given a string that is a valid path, creates the directory. + *

+ * calls {@link ResourceHelper#createDirectory(Path)} + */ + public static Path createDirectory(String directory) { + Path dirPath = Path.of(directory); + + return createDirectory(dirPath); + } + + /** + * Given a base directory path and a sub directory, resolves the sub directory + * against the base path and calls {@link ResourceHelper#createDirectory(Path)} + *

+ * returns the resolved path + */ + public static Path createDirectory(Path baseDirPath, String subDir) { + Path dirPath = baseDirPath.resolve(subDir); + + return createDirectory(dirPath); + } + + /** + * Given a base directory and a sub directory, resolves the sub directory + * against the base path and calls {@link ResourceHelper#createDirectory(Path)} + *

+ * returns the resolved path + */ + public static Path createDirectory(String baseDir, String subDir) { + Path dirPath = Path.of(baseDir, subDir); + + return createDirectory(dirPath); + } + + /** + * Given a directory path and a file name, creates a file with the given name in + * the given directory. + * + * @throws RuntimeException if the file cannot be created. Note that the + * RuntimeException wraps the thrown + * {@link IOException} + */ + public static void createFile(Path directory, String fileName) { + + File file = directory.resolve(fileName).toFile(); + + if (file.exists()) { + return; + } + + _createFile(file); + } + + /** + * Given a directory path and a file name, creates a file with the given name in + * the given directory. + *

+ * Deletes the file if it exists before creating the file. + * + * @throws RuntimeException if the file cannot be created. Note that the + * RuntimeException wraps the thrown + * {@link IOException} + */ + public static void createNewFile(Path directory, String fileName) { + + File file = directory.resolve(fileName).toFile(); + + if (file.exists()) { + file.delete(); + } + + _createFile(file); + } + + /** + * Given a file path, validates that the file exists. + *

+ * calls {@link ResourceHelper#validateFilePath(Path)} + * + * @throws ContractException {@link ResourceError#FILE_PATH_IS_DIRECTORY} if the + * file path refers to a directory + */ + public static Path validateFile(String file) { + Path filePath = Path.of(file); - return makeOutputDir(dirPath); + validateFile(filePath); + + return filePath; } - public static void createOutputFile(Path filePath, String fileName) { + /** + * Given a file path, validates that the file exists. + * + * @throws ContractException {@link ResourceError#FILE_PATH_IS_DIRECTORY} if the + * file path refers to a directory + */ + public static Path validateFile(Path filePath) { + File file = filePath.toFile(); - File isAfile = filePath.resolve(fileName).toFile(); + if (file.isDirectory()) { + throw new ContractException(ResourceError.FILE_PATH_IS_DIRECTORY); + } - if (isAfile.exists()) { - isAfile.delete(); + if (!file.exists()) { + throw new ContractException(ResourceError.UNKNOWN_FILE); } - createFile(isAfile); + return filePath; + } + + /** + * Given a file path, validates that the file exists. + *

+ * calls {@link ResourceHelper#validateFilePath(Path)} + * + * @throws ContractException {@link ResourceError#FILE_PATH_IS_DIRECTORY} if the + * file path refers to a directory + */ + public static Path validateFilePath(String file) { + Path filePath = Path.of(file); + + validateFilePath(filePath); + + return filePath; } - public static Path validatePath(String path, boolean isOutput) { - Path maybePath = Path.of(path); - File maybeFile = maybePath.toFile(); + /** + * Given a file path, validates that the file exists. + * + * @throws ContractException {@link ResourceError#FILE_PATH_IS_DIRECTORY} if the + * file path refers to a directory + */ + public static Path validateFilePath(Path filePath) { + File file = filePath.toFile(); - boolean isDirectory = maybeFile.isDirectory(); - boolean isFile = maybeFile.isFile(); + if (file.isDirectory()) { + throw new ContractException(ResourceError.FILE_PATH_IS_DIRECTORY); + } - // if the given string corresponds to a file that exists, return path - if (isFile) { - return maybePath; + if (!file.exists()) { + validateDirectoryPath(filePath.getParent()); } - // if file does not exist, ensure the path is not a directory and that the - // parent directory of the outputFile exists. - if (isOutput && !isDirectory) { - Path parentPath = maybePath.getParent(); + return filePath; + } + + /** + * Given a directory path, validates that the directory exists. + *

+ * calls {@link ResourceHelper#validateDirectoryPath(Path)} + * + * @throws ContractException {@link ResourceError#DIRECTORY_PATH_IS_FILE} if the + * directory path refers to a file + */ + public static Path validateDirectoryPath(String directory) { + Path maybePath = Path.of(directory); + + validateDirectoryPath(maybePath); + + return maybePath; + } + + /** + * Given a directory path, validates that the directory exists. + * + * @throws ContractException {@link ResourceError#DIRECTORY_PATH_IS_FILE} if the + * directory path refers to a file + */ + public static Path validateDirectoryPath(Path directoryPath) { + File maybeFile = directoryPath.toFile(); + + if (maybeFile.isFile()) { + throw new ContractException(ResourceError.DIRECTORY_PATH_IS_FILE); + } - if (Files.exists(parentPath)) { - return maybePath; - } + if (!maybeFile.exists()) { + createDirectory(directoryPath); } - // otherwise throw an exception - throw new ContractException(ResourceError.UNKNOWN_FILE, path); + return directoryPath; + } + + /** + * Given a url, converts it to a URI + */ + protected static URI getURI(URL url) { + try { + return url.toURI(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } } - protected static void createFile(File file) { + /** + * Given a file, attempts to create the file + * + * package access for testing + */ + static void _createFile(File file) { try { file.createNewFile(); } catch (IOException e) { @@ -84,4 +268,7 @@ protected static void createFile(File file) { } } + private ResourceHelper() { + } + } diff --git a/src/test/java/gov/hhs/aspr/ms/util/resourcehelper/AT_ResourceHelper.java b/src/test/java/gov/hhs/aspr/ms/util/resourcehelper/AT_ResourceHelper.java index 5354b7f..88899bd 100644 --- a/src/test/java/gov/hhs/aspr/ms/util/resourcehelper/AT_ResourceHelper.java +++ b/src/test/java/gov/hhs/aspr/ms/util/resourcehelper/AT_ResourceHelper.java @@ -1,6 +1,7 @@ package gov.hhs.aspr.ms.util.resourcehelper; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -19,6 +20,29 @@ public class AT_ResourceHelper { + private class IOExceptionFile extends File { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public IOExceptionFile(String pathname) { + super(pathname); + } + + public IOExceptionFile(File file) { + super(file, pathSeparator); + } + + @Override + public boolean createNewFile() throws IOException { + + throw new IOException(); + } + + } + @Test @UnitTestMethod(target = ResourceHelper.class, name = "getResourceDir", args = { Class.class }) public void testGetResourceDir() throws MalformedURLException { @@ -40,105 +64,296 @@ public void testGetResourceDir() throws MalformedURLException { } @Test - @UnitTestMethod(target = ResourceHelper.class, name = "makeOutputDir", args = { Path.class }) - public void testMakeTestOutputDir() { + @UnitTestMethod(target = ResourceHelper.class, name = "createDirectory", args = { Path.class }) + public void testCreateDirectory() { Path path = ResourceHelper.getResourceDir(this.getClass()).resolve("additional-folder"); - Path newPath = ResourceHelper.makeOutputDir(path); + Path newPath = ResourceHelper.createDirectory(path); - assertNotNull(newPath); assertTrue(newPath.toFile().exists()); } @Test - @UnitTestMethod(target = ResourceHelper.class, name = "makeOutputDir", args = { Path.class, String.class }) - public void testMakeTestOutputDir_SubDir() { + @UnitTestMethod(target = ResourceHelper.class, name = "createDirectory", args = { String.class }) + public void testCreateDirectory_String() { Path path = ResourceHelper.getResourceDir(this.getClass()).resolve("additional-folder"); - Path newPath = ResourceHelper.makeOutputDir(path, "subFolder"); + Path newPath = ResourceHelper.createDirectory(path.toString()); - assertNotNull(newPath); assertTrue(newPath.toFile().exists()); } - private class IOExceptionFile extends File { - - /** - * - */ - private static final long serialVersionUID = 1L; + @Test + @UnitTestMethod(target = ResourceHelper.class, name = "createDirectory", args = { Path.class, String.class }) + public void testCreateDirectory_Path_String() { + Path path = ResourceHelper.getResourceDir(this.getClass()); - public IOExceptionFile(String pathname) { - super(pathname); - } + Path newPath = ResourceHelper.createDirectory(path, "additional-folder"); - public IOExceptionFile(File file) { - super(file, pathSeparator); - } + assertTrue(newPath.toFile().exists()); + } - @Override - public boolean createNewFile() throws IOException { + @Test + @UnitTestMethod(target = ResourceHelper.class, name = "createDirectory", args = { String.class, String.class }) + public void testCreateDirectory_String_String() { + Path path = ResourceHelper.getResourceDir(this.getClass()); - throw new IOException(); - } + Path newPath = ResourceHelper.createDirectory(path.toString(), "additional-folder"); + assertTrue(newPath.toFile().exists()); } @Test - @UnitTestMethod(target = ResourceHelper.class, name = "createOutputFile", args = { Path.class, - String.class }) - public void testCreateTestOutputFile() throws IOException { + @UnitTestMethod(target = ResourceHelper.class, name = "createFile", args = { Path.class, String.class }) + public void testCreateFile() throws IOException { Path path = ResourceHelper.getResourceDir(this.getClass()); - Path newPath = ResourceHelper.makeOutputDir(path); + Path newPath = ResourceHelper.createDirectory(path); String fileName = "foo.txt"; - ResourceHelper.createOutputFile(newPath, fileName); + ResourceHelper.createFile(newPath, fileName); assertTrue(newPath.resolve(fileName).toFile().exists()); - // test for delete/recreat file if it exists - ResourceHelper.createOutputFile(newPath, fileName); + // preconditions + // the file cannot be created + // force an IOException + RuntimeException runtimeException = assertThrows(RuntimeException.class, () -> { + IOExceptionFile testFile = new IOExceptionFile(newPath.resolve(fileName).toFile()); + ResourceHelper._createFile(testFile); + }); + + assertTrue(runtimeException.getCause() instanceof IOException); + + newPath.resolve(fileName).toFile().delete(); + } + + @Test + @UnitTestMethod(target = ResourceHelper.class, name = "createNewFile", args = { Path.class, String.class }) + public void testCreateNewFile() throws IOException { + Path path = ResourceHelper.getResourceDir(this.getClass()); + + Path newPath = ResourceHelper.createDirectory(path); + + String fileName = "foo.txt"; + ResourceHelper.createNewFile(newPath, fileName); + + assertTrue(newPath.resolve(fileName).toFile().exists()); // preconditions + // the file cannot be created // force an IOException RuntimeException runtimeException = assertThrows(RuntimeException.class, () -> { IOExceptionFile testFile = new IOExceptionFile(newPath.resolve(fileName).toFile()); - ResourceHelper.createFile(testFile); + ResourceHelper._createFile(testFile); }); assertTrue(runtimeException.getCause() instanceof IOException); + + newPath.resolve(fileName).toFile().delete(); } @Test - @UnitTestMethod(target = ResourceHelper.class, name = "validatePath", args = { String.class, boolean.class }) - public void testValidatePath() { + @UnitTestMethod(target = ResourceHelper.class, name = "validateFile", args = { String.class }) + public void testValidateFile() { Path resourceDir = ResourceHelper.getResourceDir(getClass()); - // path is a file that is not output and exists - String path = resourceDir.resolve("resourceHelper.json").toAbsolutePath().toString(); - Path expectedPath = resourceDir.resolve("resourceHelper.json").toAbsolutePath(); - assertEquals(expectedPath, ResourceHelper.validatePath(path, false)); - - // file is an output file that does not exist, but the directory the file - // resides in exists - ResourceHelper.makeOutputDir(resourceDir, "testOutput"); - path = resourceDir.resolve("testOutput").resolve("resourceHelper.json").toAbsolutePath().toString(); - expectedPath = resourceDir.resolve("testOutput").resolve("resourceHelper.json").toAbsolutePath(); - assertEquals(expectedPath, ResourceHelper.validatePath(path, true)); + Path dirPath = resourceDir; + String fileName = "validateFile.json"; + + ResourceHelper.createFile(dirPath, fileName); + + String filePath = dirPath.resolve(fileName).toAbsolutePath().toString(); + assertTrue(ResourceHelper.validateFile(filePath).toFile().exists()); // preconditions - // File does not exist and it is not an outputFile + // file path points to a directory ContractException contractException = assertThrows(ContractException.class, () -> { - ResourceHelper.validatePath("testOutput", false); + ResourceHelper.validateFile(resourceDir.toString()); + }); + + assertEquals(ResourceError.FILE_PATH_IS_DIRECTORY, contractException.getErrorType()); + + // file does not exist + contractException = assertThrows(ContractException.class, () -> { + ResourceHelper.validateFile(dirPath.resolve("unknwonfile.txt")); }); assertEquals(ResourceError.UNKNOWN_FILE, contractException.getErrorType()); - // is output but parent directory does not exist + dirPath.resolve(fileName).toFile().delete(); + } + + @Test + @UnitTestMethod(target = ResourceHelper.class, name = "validateFile", args = { Path.class }) + public void testValidateFile_Path() { + Path resourceDir = ResourceHelper.getResourceDir(getClass()); + Path dirPath = resourceDir; + String fileName = "validateFile.json"; + + ResourceHelper.createFile(dirPath, fileName); + + Path filePath = dirPath.resolve(fileName).toAbsolutePath(); + assertTrue(ResourceHelper.validateFile(filePath).toFile().exists()); + + // preconditions + // file path points to a directory + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceHelper.validateFile(resourceDir.toString()); + }); + + assertEquals(ResourceError.FILE_PATH_IS_DIRECTORY, contractException.getErrorType()); + + // file does not exist contractException = assertThrows(ContractException.class, () -> { - ResourceHelper.validatePath(resourceDir.resolve("nonExistantDir").resolve("file.json").toString(), true); + ResourceHelper.validateFile(dirPath.resolve("unknwonfile.txt")); }); assertEquals(ResourceError.UNKNOWN_FILE, contractException.getErrorType()); + + dirPath.resolve(fileName).toFile().delete(); + } + + @Test + @UnitTestMethod(target = ResourceHelper.class, name = "validateFilePath", args = { String.class }) + public void testValidateFilePath() { + Path resourceDir = ResourceHelper.getResourceDir(getClass()); + Path dirPath = resourceDir; + String fileName = "validateFilePath.json"; + String subDirName = "temp"; + + // delete any old test files + dirPath.resolve(fileName).toFile().delete(); + + // path is a file that does not exist but directory exists + String filePath = dirPath.resolve(fileName).toAbsolutePath().toString(); + Path path = ResourceHelper.validateFilePath(filePath); + assertFalse(path.toFile().exists()); + + // create file + ResourceHelper.createFile(dirPath, fileName); + + // path is a file that does exist + path = ResourceHelper.validateFilePath(filePath); + assertTrue(path.toFile().exists()); + + // path is a file that does not exist and the directory does not exist + dirPath = resourceDir.resolve(subDirName); + filePath = dirPath.resolve(fileName).toAbsolutePath().toString(); + path = ResourceHelper.validateFilePath(filePath); + // file does not exist + assertFalse(path.toFile().exists()); + // directory exists because it was created + assertTrue(dirPath.toFile().exists()); + + // preconditions + // File path points to a directory + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceHelper.validateFilePath(resourceDir); + }); + + assertEquals(ResourceError.FILE_PATH_IS_DIRECTORY, contractException.getErrorType()); + + resourceDir.resolve(fileName).toFile().delete(); + dirPath.resolve(fileName).toFile().delete(); + } + + @Test + @UnitTestMethod(target = ResourceHelper.class, name = "validateFilePath", args = { Path.class }) + public void testValidateFilePath_Path() { + Path resourceDir = ResourceHelper.getResourceDir(getClass()); + Path dirPath = resourceDir; + String fileName = "validateFilePath.json"; + String subDirName = "temp"; + + // delete any old test files + dirPath.resolve(fileName).toFile().delete(); + + // path is a file that does not exist but directory exists + Path filePath = dirPath.resolve(fileName).toAbsolutePath(); + Path path = ResourceHelper.validateFilePath(filePath); + assertFalse(path.toFile().exists()); + + // create file + ResourceHelper.createFile(dirPath, fileName); + + // path is a file that does exist + path = ResourceHelper.validateFilePath(filePath); + assertTrue(path.toFile().exists()); + + // path is a file that does not exist and the directory does not exist + dirPath = resourceDir.resolve(subDirName); + filePath = dirPath.resolve(fileName).toAbsolutePath(); + path = ResourceHelper.validateFilePath(filePath); + // file does not exist + assertFalse(path.toFile().exists()); + // directory exists because it was created + assertTrue(dirPath.toFile().exists()); + + // preconditions + // File path points to a directory + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceHelper.validateFilePath(resourceDir); + }); + + assertEquals(ResourceError.FILE_PATH_IS_DIRECTORY, contractException.getErrorType()); + + resourceDir.resolve(fileName).toFile().delete(); + dirPath.resolve(fileName).toFile().delete(); + } + + @Test + @UnitTestMethod(target = ResourceHelper.class, name = "validateDirectoryPath", args = { String.class }) + public void testValidateDirectoryPath() { + Path resourceDir = ResourceHelper.getResourceDir(getClass()); + Path dirPath = resourceDir; + String subDirName = "temp"; + + // delete any old test files + dirPath.resolve(subDirName).toFile().delete(); + + // path is a file that does not exist but directory exists + String filePath = dirPath.resolve(subDirName).toAbsolutePath().toString(); + Path path = ResourceHelper.validateDirectoryPath(filePath); + assertTrue(path.toFile().exists()); + + // preconditions + // directory path points to a file + String fileName = "validateDirectoryPath.json"; + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceHelper.createFile(dirPath, fileName); + ResourceHelper.validateDirectoryPath(dirPath.resolve(fileName).toString()); + }); + + assertEquals(ResourceError.DIRECTORY_PATH_IS_FILE, contractException.getErrorType()); + + dirPath.resolve(fileName).toFile().delete(); + } + + @Test + @UnitTestMethod(target = ResourceHelper.class, name = "validateDirectoryPath", args = { Path.class }) + public void testValidateDirectoryPath_Path() { + Path resourceDir = ResourceHelper.getResourceDir(getClass()); + Path dirPath = resourceDir; + String subDirName = "temp"; + + // delete any old test files + dirPath.resolve(subDirName).toFile().delete(); + + // path is a file that does not exist but directory exists + Path filePath = dirPath.resolve(subDirName).toAbsolutePath(); + Path path = ResourceHelper.validateDirectoryPath(filePath); + assertTrue(path.toFile().exists()); + + // preconditions + // directory path points to a file + String fileName = "validateDirectoryPath.json"; + ContractException contractException = assertThrows(ContractException.class, () -> { + ResourceHelper.createFile(dirPath, fileName); + ResourceHelper.validateDirectoryPath(dirPath.resolve(fileName)); + }); + + assertEquals(ResourceError.DIRECTORY_PATH_IS_FILE, contractException.getErrorType()); + + dirPath.resolve(fileName).toFile().delete(); } }