diff --git a/build.gradle.kts b/build.gradle.kts index 22e7087..0d8dffd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -50,7 +50,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-devtools:${springBootVersion}") testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") - testImplementation("io.mockk:mockk:1.12.0") + testImplementation("io.mockk:mockk:1.12.1") testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}") testImplementation("org.mockito:mockito-inline:3.11.2") testImplementation("org.springframework:spring-test:5.3.13") @@ -68,8 +68,8 @@ springBoot { } java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } sourceSets { @@ -83,15 +83,18 @@ sourceSets { } tasks.test { + useJUnitPlatform() + + jvmArgs("-XX:+ShowCodeDetailsInExceptionMessages") + testLogging { + events("skipped", "failed") + setExceptionFormat("full") + } + reports { junitXml.required.set(true) html.required.set(false) } - - useJUnitPlatform() - testLogging { - events("skipped", "failed") - } } tasks.withType() { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileExistCache.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileExistCache.kt new file mode 100644 index 0000000..46abd2e --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileExistCache.kt @@ -0,0 +1,23 @@ +package dev.fyloz.colorrecipesexplorer.service.files + +import dev.fyloz.colorrecipesexplorer.utils.FilePath + +class FileExistCache { + private val map = hashMapOf() + + operator fun contains(path: FilePath) = + path in map + + fun exists(path: FilePath) = + map[path] ?: false + + fun set(path: FilePath, exists: Boolean) { + map[path] = exists + } + + fun setExists(path: FilePath) = + set(path, true) + + fun setDoesNotExists(path: FilePath) = + set(path, false) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileService.kt index 4136ebe..683cb74 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileService.kt @@ -2,6 +2,9 @@ package dev.fyloz.colorrecipesexplorer.service.files import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties import dev.fyloz.colorrecipesexplorer.exception.RestException +import dev.fyloz.colorrecipesexplorer.utils.FilePath +import dev.fyloz.colorrecipesexplorer.utils.WrappedFile +import dev.fyloz.colorrecipesexplorer.utils.withFileAt import org.slf4j.Logger import org.springframework.core.io.ByteArrayResource import org.springframework.core.io.Resource @@ -49,8 +52,17 @@ class FileServiceImpl( private val creProperties: CreProperties, private val logger: Logger ) : WriteableFileService { - override fun exists(path: String) = withFileAt(path.fullPath()) { - this.exists() && this.isFile + private val existsCache = FileExistCache() + + override fun exists(path: String): Boolean { + val fullPath = path.fullPath() + return if (fullPath in existsCache) { + existsCache.exists(fullPath) + } else { + withFileAt(fullPath) { + this.exists() && this.isFile + } + } } override fun read(path: String) = ByteArrayResource( @@ -70,6 +82,7 @@ class FileServiceImpl( try { withFileAt(fullPath) { this.create() + existsCache.setExists(fullPath) } } catch (ex: IOException) { FileCreateException(path).logAndThrow(ex, logger) @@ -89,9 +102,12 @@ class FileServiceImpl( override fun delete(path: String) { try { - withFileAt(path.fullPath()) { + val fullPath = path.fullPath() + withFileAt(fullPath) { if (!exists(path)) throw FileNotFoundException(path) - !this.delete() + + this.delete() + existsCache.setDoesNotExists(fullPath) } } catch (ex: IOException) { FileDeleteException(path).logAndThrow(ex, logger) @@ -106,7 +122,7 @@ class FileServiceImpl( return FilePath("${creProperties.dataDirectory}/$this") } - private fun prepareWrite(path: String, overwrite: Boolean, op: File.() -> Unit) { + private fun prepareWrite(path: String, overwrite: Boolean, op: WrappedFile.() -> Unit) { val fullPath = path.fullPath() if (exists(path)) { @@ -123,15 +139,6 @@ class FileServiceImpl( FileWriteException(path).logAndThrow(ex, logger) } } - - /** Runs the given [block] in the context of a file with the given [fullPath]. */ - private fun withFileAt(fullPath: FilePath, block: File.() -> T) = - fullPath.file.block() -} - -data class FilePath(val path: String) { - val file: File - get() = File(path) } /** Shortcut to create a file and its parent directories. */ diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt index be9ba6e..d33bc9e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt @@ -1,5 +1,6 @@ package dev.fyloz.colorrecipesexplorer.service.files +import dev.fyloz.colorrecipesexplorer.utils.FilePath import org.springframework.core.io.Resource import org.springframework.core.io.ResourceLoader import org.springframework.stereotype.Service diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Files.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Files.kt new file mode 100644 index 0000000..8e08f4f --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Files.kt @@ -0,0 +1,44 @@ +package dev.fyloz.colorrecipesexplorer.utils + +import dev.fyloz.colorrecipesexplorer.service.files.create +import java.io.File +import java.nio.file.Path + +/** Mockable file wrapper, to prevent issues when mocking [java.io.File]. */ +class WrappedFile(val file: File) { + val isFile: Boolean + get() = file.isFile + + fun toPath(): Path = + file.toPath() + + fun exists() = + file.exists() + + fun readBytes() = + file.readBytes() + + fun writeBytes(array: ByteArray) = + file.writeBytes(array) + + fun create() = + file.create() + + fun delete(): Boolean = + file.delete() + + companion object { + fun from(path: String) = + WrappedFile(File(path)) + + fun from(path: FilePath) = + from(path.path) + } +} + +@JvmInline +value class FilePath(val path: String) + +/** Runs the given [block] in the context of a file with the given [fullPath]. */ +fun withFileAt(fullPath: FilePath, block: WrappedFile.() -> T) = + WrappedFile.from(fullPath).block() \ No newline at end of file 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 936bf47..a68a93d 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileServiceTest.kt @@ -1,6 +1,7 @@ package dev.fyloz.colorrecipesexplorer.service.files import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties +import dev.fyloz.colorrecipesexplorer.utils.WrappedFile import io.mockk.* import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test @@ -24,20 +25,18 @@ private class FileServiceTestContext { val fileService = spyk(FileServiceImpl(creProperties, mockk { every { error(any(), any()) } just Runs })) - val mockFile = mockk { - every { path } returns mockFilePath + val mockFile = mockk { + every { file } returns mockk() every { exists() } returns true every { isFile } returns true every { toPath() } returns mockFilePathPath } - val mockFileFullPath = spyk(FilePath("${creProperties.dataDirectory}/$mockFilePath")) { - every { file } returns mockFile - - with(fileService) { - every { mockFilePath.fullPath() } returns this@spyk - } - } val mockMultipartFile = spyk(MockMultipartFile(mockFilePath, mockFileData)) + + init { + mockkObject(WrappedFile.Companion) + every { WrappedFile.from(any()) } returns mockFile + } } class FileServiceTest { diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileServiceTest.kt index 5c0d6ff..9aafde9 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileServiceTest.kt @@ -1,5 +1,6 @@ package dev.fyloz.colorrecipesexplorer.service.files +import dev.fyloz.colorrecipesexplorer.utils.FilePath import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk