diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileServiceTest.kt index 756d7de..972a845 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileServiceTest.kt @@ -1,118 +1,293 @@ package dev.fyloz.colorrecipesexplorer.service.files -import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties +import io.mockk.* import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test -import org.slf4j.Logger -import org.springframework.core.io.Resource -import org.springframework.core.io.ResourceLoader +import org.junit.jupiter.api.assertThrows +import org.springframework.mock.web.MockMultipartFile import org.springframework.web.multipart.MultipartFile import java.io.File import java.io.IOException -import java.io.InputStream -import java.nio.file.Paths +import java.nio.file.Path import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +private val creProperties = CreProperties().apply { + workingDirectory = "data" + deploymentUrl = "http://localhost" +} +private const val mockFilePath = "existingFile" +private val mockFilePathPath = Path.of(mockFilePath) +private val mockFileData = byteArrayOf(0x1, 0x8, 0xa, 0xf) + +private class FileServiceTestContext { + val fileService: FileService + val mockFile: File + val mockFileFullPath: FilePath + val mockMultipartFile: MultipartFile + + init { + fileService = spyk(FileServiceImpl(creProperties, mockk { + every { error(any(), any()) } just Runs + })) + + mockFile = mockk { + every { path } returns mockFilePath + every { exists() } returns true + every { isFile } returns true + every { toPath() } returns mockFilePathPath + } + + mockFileFullPath = spyk(FilePath("${creProperties.workingDirectory}/$mockFilePath")) { + every { file } returns mockFile + + with(fileService) { + every { mockFilePath.fullPath() } returns this@spyk + } + } + + mockMultipartFile = spyk(MockMultipartFile(mockFilePath, mockFileData)) + } +} + class FileServiceTest { - private val resourcesLoader = mock() - private val logger = mock() - private val properties = CreProperties() - - private val service = spy(FileService(resourcesLoader, properties, logger)) - - private val path = "/var/cre/file" - @AfterEach - fun afterEach() { - reset(resourcesLoader, logger, service) + internal fun afterEach() { + clearAllMocks() } - // readResource() + // exists() @Test - fun `readResource() returns content of the resource at the given path`() { - val resource = mock() - val resourceStream = mock() - val resourceContent = """ - Line 1 - Line 2 - Line 3 - """.trimIndent() - - whenever(resource.inputStream).doReturn(resourceStream) - whenever(resourcesLoader.getResource("classpath:$path")).doReturn(resource) - doReturn(resourceContent).whenever(service).readInputStreamAsString(resourceStream) - - val found = service.readResource(path) - - assertEquals(resourceContent, found) + fun `exists() returns true when the file at the given path exists and is a file`() { + fileServiceTest { + assertTrue { fileService.exists(mockFilePath) } + } } - // readInputStreamAsString() + @Test + fun `exists() returns false when the file at the given path does not exist`() { + fileServiceTest { + every { mockFile.exists() } returns false + + assertFalse { fileService.exists(mockFilePath) } + } + } @Test - fun `readInputStreamAsString() returns a String matching the given input stream's content`() { - val stream = mock() - val streamContent = """ - Line 1 - Line 2 - Line 3 - """.trimIndent() + fun `exists() returns false when the file at the given path is not a file`() { + fileServiceTest { + every { mockFile.isFile } returns false - whenever(stream.readAllBytes()).doAnswer { streamContent.toByteArray() } + assertFalse { fileService.exists(mockFilePath) } + } + } - val found = service.readInputStreamAsString(stream) + // read() - assertEquals(streamContent, found) + @Test + fun `read() returns a valid ByteArrayResource`() { + fileServiceTest { + whenMockFilePathExists { + mockkStatic(File::readBytes) + every { mockFile.readBytes() } returns mockFileData + + val redResource = fileService.read(mockFilePath) + + assertEquals(mockFileData, redResource.byteArray) + } + } + } + + @Test + fun `read() throws FileNotFoundException when no file exists at the given path`() { + fileServiceTest { + whenMockFilePathExists(false) { + with(assertThrows { fileService.read(mockFilePath) }) { + assertEquals(mockFilePath, this.path) + } + } + } + } + + @Test + fun `read() throws FileReadException when an IOException is thrown`() { + fileServiceTest { + whenMockFilePathExists { + mockkStatic(File::readBytes) + every { mockFile.readBytes() } throws IOException() + + with(assertThrows { fileService.read(mockFilePath) }) { + assertEquals(mockFilePath, this.path) + } + } + } + } + + // create() + + @Test + fun `create() creates a file at the given path`() { + fileServiceTest { + whenMockFilePathExists(false) { + mockkStatic(File::create) + every { mockFile.create() } just Runs + + fileService.create(mockFilePath) + + verify { + mockFile.create() + } + } + } + } + + @Test + fun `create() does nothing when a file already exists at the given path`() { + fileServiceTest { + whenMockFilePathExists { + fileService.create(mockFilePath) + + verify(exactly = 0) { + mockFile.create() + } + } + } + } + + @Test + fun `create() throws FileCreateException when the file creation throws an IOException`() { + fileServiceTest { + whenMockFilePathExists(false) { + mockkStatic(File::create) + every { mockFile.create() } throws IOException() + + with(assertThrows { fileService.create(mockFilePath) }) { + assertEquals(mockFilePath, this.path) + } + } + } } // write() - private inline fun withMultipartFile(size: Long = 1000L, test: (MultipartFile) -> Unit) { - val multipartFile = mock() - whenever(multipartFile.size).doReturn(size) - - test(multipartFile) - } - @Test - fun `write() transfers data from the given MultipartFile to the file at the given path and returns true`() { - withMultipartFile { multipartFile -> - val file = mock() - val filePath = Paths.get(path) + fun `write() creates and writes the given MultipartFile to the file at the given path`() { + fileServiceTest { + whenMockFilePathExists(false) { + every { fileService.create(mockFilePath) } just Runs + every { mockMultipartFile.transferTo(mockFilePathPath) } just Runs - whenever(file.toPath()).doReturn(filePath) - doAnswer { file }.whenever(service).create(path) + fileService.write(mockMultipartFile, mockFilePath, false) - assertTrue { service.write(multipartFile, path) } - - verify(multipartFile).transferTo(filePath) + verify { + fileService.create(mockFilePath) + mockMultipartFile.transferTo(mockFilePathPath) + } + } } } @Test - fun `write() returns true when given MultipartFile is empty`() { - withMultipartFile(size = 0L) { multipartFile -> - assertTrue { service.write(multipartFile, path) } - - verify(multipartFile, never()).transferTo(any()) + fun `write() throws FileExistsException when a file at the given path already exists and overwrite is disabled`() { + fileServiceTest { + whenMockFilePathExists { + with(assertThrows { fileService.write(mockMultipartFile, mockFilePath, false) }) { + assertEquals(mockFilePath, this.path) + } + } } } @Test - fun `write() returns false when the data transfer throw an IOException`() { - withMultipartFile { multipartFile -> - val file = mock() - val filePath = Paths.get(path) + fun `write() writes the given MultipartFile to an existing file when overwrite is enabled`() { + fileServiceTest { + whenMockFilePathExists { + every { mockMultipartFile.transferTo(mockFilePathPath) } just Runs - whenever(file.toPath()).doReturn(filePath) - whenever(multipartFile.transferTo(filePath)).doThrow(IOException()) - doAnswer { file }.whenever(service).create(path) + fileService.write(mockMultipartFile, mockFilePath, true) - assertFalse { service.write(multipartFile, path) } + verify { + mockMultipartFile.transferTo(mockFilePathPath) + } + } } } + + @Test + fun `write() throws FileWriteException when writing the given file throws an IOException`() { + fileServiceTest { + whenMockFilePathExists(false) { + every { fileService.create(mockFilePath) } just Runs + every { mockMultipartFile.transferTo(mockFilePathPath) } throws IOException() + + with(assertThrows { + fileService.write(mockMultipartFile, mockFilePath, false) + }) { + assertEquals(mockFilePath, this.path) + } + } + } + } + + // delete() + + @Test + fun `delete() deletes the file at the given path`() { + fileServiceTest { + whenMockFilePathExists { + every { mockFile.delete() } returns true + + fileService.delete(mockFilePath) + } + } + } + + @Test + fun `delete() throws FileNotFoundException when no file exists at the given path`() { + fileServiceTest { + whenMockFilePathExists(false) { + with(assertThrows { fileService.delete(mockFilePath) }) { + assertEquals(mockFilePath, this.path) + } + } + } + } + + @Test + fun `delete() throws FileDeleteException when deleting throw and IOException`() { + fileServiceTest { + whenMockFilePathExists { + every { mockFile.delete() } throws IOException() + + with(assertThrows { fileService.delete(mockFilePath) }) { + assertEquals(mockFilePath, this.path) + } + } + } + } + + // String.fullPath() + + @Test + fun `fullPath() appends the given path to the given working directory`() { + fileServiceTest { + with(fileService) { + val fullFilePath = mockFilePath.fullPath() + + assertEquals("${creProperties.workingDirectory}/$mockFilePath", fullFilePath.path) + } + } + } + + private fun fileServiceTest(test: FileServiceTestContext.() -> Unit) { + FileServiceTestContext().test() + } + + private fun FileServiceTestContext.whenMockFilePathExists(exists: Boolean = true, test: () -> Unit) { + every { fileService.exists(mockFilePath) } returns exists + test() + } }