This commit is contained in:
parent
eae3aecb31
commit
bb069512b4
|
@ -1,5 +1,5 @@
|
||||||
ARG GRADLE_VERSION=7.1
|
ARG GRADLE_VERSION=7.3
|
||||||
ARG JAVA_VERSION=11
|
ARG JAVA_VERSION=17
|
||||||
|
|
||||||
FROM gradle:$GRADLE_VERSION-jdk$JAVA_VERSION AS build
|
FROM gradle:$GRADLE_VERSION-jdk$JAVA_VERSION AS build
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
|
@ -98,7 +98,7 @@ tasks.test {
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<JavaCompile>() {
|
tasks.withType<JavaCompile>() {
|
||||||
options.compilerArgs.addAll(arrayOf("--release", "11"))
|
options.compilerArgs.addAll(arrayOf("--release", "17"))
|
||||||
}
|
}
|
||||||
tasks.withType<KotlinCompile>().all {
|
tasks.withType<KotlinCompile>().all {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
|
|
@ -3,3 +3,5 @@ package dev.fyloz.colorrecipesexplorer
|
||||||
typealias SpringUser = org.springframework.security.core.userdetails.User
|
typealias SpringUser = org.springframework.security.core.userdetails.User
|
||||||
typealias SpringUserDetails = org.springframework.security.core.userdetails.UserDetails
|
typealias SpringUserDetails = org.springframework.security.core.userdetails.UserDetails
|
||||||
typealias SpringUserDetailsService = org.springframework.security.core.userdetails.UserDetailsService
|
typealias SpringUserDetailsService = org.springframework.security.core.userdetails.UserDetailsService
|
||||||
|
|
||||||
|
typealias JavaFile = java.io.File
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.service.config
|
package dev.fyloz.colorrecipesexplorer.service.config
|
||||||
|
|
||||||
|
import dev.fyloz.colorrecipesexplorer.JavaFile
|
||||||
import dev.fyloz.colorrecipesexplorer.SUPPORTED_DATABASE_VERSION
|
import dev.fyloz.colorrecipesexplorer.SUPPORTED_DATABASE_VERSION
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
||||||
import dev.fyloz.colorrecipesexplorer.emergencyMode
|
import dev.fyloz.colorrecipesexplorer.emergencyMode
|
||||||
|
@ -8,14 +9,13 @@ import dev.fyloz.colorrecipesexplorer.model.Configuration
|
||||||
import dev.fyloz.colorrecipesexplorer.model.ConfigurationType
|
import dev.fyloz.colorrecipesexplorer.model.ConfigurationType
|
||||||
import dev.fyloz.colorrecipesexplorer.model.configuration
|
import dev.fyloz.colorrecipesexplorer.model.configuration
|
||||||
import dev.fyloz.colorrecipesexplorer.repository.ConfigurationRepository
|
import dev.fyloz.colorrecipesexplorer.repository.ConfigurationRepository
|
||||||
import dev.fyloz.colorrecipesexplorer.service.files.create
|
import dev.fyloz.colorrecipesexplorer.utils.create
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.excludeAll
|
import dev.fyloz.colorrecipesexplorer.utils.excludeAll
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.springframework.boot.info.BuildProperties
|
import org.springframework.boot.info.BuildProperties
|
||||||
import org.springframework.context.annotation.Lazy
|
import org.springframework.context.annotation.Lazy
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
@ -96,7 +96,7 @@ private class FileConfigurationSource(
|
||||||
private val configFilePath: String
|
private val configFilePath: String
|
||||||
) : ConfigurationSource {
|
) : ConfigurationSource {
|
||||||
private val properties = Properties().apply {
|
private val properties = Properties().apply {
|
||||||
with(File(configFilePath)) {
|
with(JavaFile(configFilePath)) {
|
||||||
if (!this.exists()) this.create()
|
if (!this.exists()) this.create()
|
||||||
FileInputStream(this).use {
|
FileInputStream(this).use {
|
||||||
this@apply.load(it)
|
this@apply.load(it)
|
||||||
|
|
|
@ -2,22 +2,26 @@ package dev.fyloz.colorrecipesexplorer.service.files
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.FilePath
|
import dev.fyloz.colorrecipesexplorer.utils.FilePath
|
||||||
|
|
||||||
class FileExistCache {
|
object FileExistCache {
|
||||||
private val map = hashMapOf<FilePath, Boolean>()
|
private val map = hashMapOf<FilePath, Boolean>()
|
||||||
|
|
||||||
|
/** Checks if the given [path] is in the cache. */
|
||||||
operator fun contains(path: FilePath) =
|
operator fun contains(path: FilePath) =
|
||||||
path in map
|
path in map
|
||||||
|
|
||||||
|
/** Checks if the file at the given [path] exists. */
|
||||||
fun exists(path: FilePath) =
|
fun exists(path: FilePath) =
|
||||||
map[path] ?: false
|
map[path] ?: false
|
||||||
|
|
||||||
fun set(path: FilePath, exists: Boolean) {
|
/** Sets the file at the given [path] as existing. */
|
||||||
map[path] = exists
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setExists(path: FilePath) =
|
fun setExists(path: FilePath) =
|
||||||
set(path, true)
|
set(path, true)
|
||||||
|
|
||||||
|
/** Sets the file at the given [path] as not existing. */
|
||||||
fun setDoesNotExists(path: FilePath) =
|
fun setDoesNotExists(path: FilePath) =
|
||||||
set(path, false)
|
set(path, false)
|
||||||
|
|
||||||
|
private fun set(path: FilePath, exists: Boolean) {
|
||||||
|
map[path] = exists
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,8 +2,8 @@ package dev.fyloz.colorrecipesexplorer.service.files
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
||||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||||
|
import dev.fyloz.colorrecipesexplorer.utils.File
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.FilePath
|
import dev.fyloz.colorrecipesexplorer.utils.FilePath
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.WrappedFile
|
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.withFileAt
|
import dev.fyloz.colorrecipesexplorer.utils.withFileAt
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.springframework.core.io.ByteArrayResource
|
import org.springframework.core.io.ByteArrayResource
|
||||||
|
@ -11,9 +11,7 @@ import org.springframework.core.io.Resource
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.web.multipart.MultipartFile
|
import org.springframework.web.multipart.MultipartFile
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Files
|
|
||||||
|
|
||||||
/** Banned path shards. These are banned because they can allow access to files outside the data directory. */
|
/** Banned path shards. These are banned because they can allow access to files outside the data directory. */
|
||||||
val BANNED_FILE_PATH_SHARDS = setOf(
|
val BANNED_FILE_PATH_SHARDS = setOf(
|
||||||
|
@ -52,12 +50,10 @@ class FileServiceImpl(
|
||||||
private val creProperties: CreProperties,
|
private val creProperties: CreProperties,
|
||||||
private val logger: Logger
|
private val logger: Logger
|
||||||
) : WriteableFileService {
|
) : WriteableFileService {
|
||||||
private val existsCache = FileExistCache()
|
|
||||||
|
|
||||||
override fun exists(path: String): Boolean {
|
override fun exists(path: String): Boolean {
|
||||||
val fullPath = path.fullPath()
|
val fullPath = path.fullPath()
|
||||||
return if (fullPath in existsCache) {
|
return if (fullPath in FileExistCache) {
|
||||||
existsCache.exists(fullPath)
|
FileExistCache.exists(fullPath)
|
||||||
} else {
|
} else {
|
||||||
withFileAt(fullPath) {
|
withFileAt(fullPath) {
|
||||||
this.exists() && this.isFile
|
this.exists() && this.isFile
|
||||||
|
@ -82,7 +78,7 @@ class FileServiceImpl(
|
||||||
try {
|
try {
|
||||||
withFileAt(fullPath) {
|
withFileAt(fullPath) {
|
||||||
this.create()
|
this.create()
|
||||||
existsCache.setExists(fullPath)
|
FileExistCache.setExists(fullPath)
|
||||||
}
|
}
|
||||||
} catch (ex: IOException) {
|
} catch (ex: IOException) {
|
||||||
FileCreateException(path).logAndThrow(ex, logger)
|
FileCreateException(path).logAndThrow(ex, logger)
|
||||||
|
@ -107,7 +103,7 @@ class FileServiceImpl(
|
||||||
if (!exists(path)) throw FileNotFoundException(path)
|
if (!exists(path)) throw FileNotFoundException(path)
|
||||||
|
|
||||||
this.delete()
|
this.delete()
|
||||||
existsCache.setDoesNotExists(fullPath)
|
FileExistCache.setDoesNotExists(fullPath)
|
||||||
}
|
}
|
||||||
} catch (ex: IOException) {
|
} catch (ex: IOException) {
|
||||||
FileDeleteException(path).logAndThrow(ex, logger)
|
FileDeleteException(path).logAndThrow(ex, logger)
|
||||||
|
@ -122,7 +118,7 @@ class FileServiceImpl(
|
||||||
return FilePath("${creProperties.dataDirectory}/$this")
|
return FilePath("${creProperties.dataDirectory}/$this")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareWrite(path: String, overwrite: Boolean, op: WrappedFile.() -> Unit) {
|
private fun prepareWrite(path: String, overwrite: Boolean, op: File.() -> Unit) {
|
||||||
val fullPath = path.fullPath()
|
val fullPath = path.fullPath()
|
||||||
|
|
||||||
if (exists(path)) {
|
if (exists(path)) {
|
||||||
|
@ -141,12 +137,6 @@ class FileServiceImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shortcut to create a file and its parent directories. */
|
|
||||||
fun File.create() {
|
|
||||||
Files.createDirectories(this.parentFile.toPath())
|
|
||||||
Files.createFile(this.toPath())
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val FILE_IO_EXCEPTION_TITLE = "File IO error"
|
private const val FILE_IO_EXCEPTION_TITLE = "File IO error"
|
||||||
|
|
||||||
class InvalidFilePathException(val path: String, val fragment: String) :
|
class InvalidFilePathException(val path: String, val fragment: String) :
|
||||||
|
|
|
@ -1,44 +1,50 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.utils
|
package dev.fyloz.colorrecipesexplorer.utils
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.service.files.create
|
import dev.fyloz.colorrecipesexplorer.JavaFile
|
||||||
import java.io.File
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
/** Mockable file wrapper, to prevent issues when mocking [java.io.File]. */
|
/** Mockable file wrapper, to prevent issues when mocking [java.io.File]. */
|
||||||
class WrappedFile(val file: File) {
|
class File(val file: JavaFile) {
|
||||||
val isFile: Boolean
|
val isFile: Boolean
|
||||||
get() = file.isFile
|
get() = file.isFile
|
||||||
|
|
||||||
fun toPath(): Path =
|
fun toPath(): Path =
|
||||||
file.toPath()
|
file.toPath()
|
||||||
|
|
||||||
fun exists() =
|
fun exists() =
|
||||||
file.exists()
|
file.exists()
|
||||||
|
|
||||||
fun readBytes() =
|
fun readBytes() =
|
||||||
file.readBytes()
|
file.readBytes()
|
||||||
|
|
||||||
fun writeBytes(array: ByteArray) =
|
fun writeBytes(array: ByteArray) =
|
||||||
file.writeBytes(array)
|
file.writeBytes(array)
|
||||||
|
|
||||||
fun create() =
|
fun create() =
|
||||||
file.create()
|
file.create()
|
||||||
|
|
||||||
fun delete(): Boolean =
|
fun delete(): Boolean =
|
||||||
file.delete()
|
file.delete()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(path: String) =
|
fun from(path: String) =
|
||||||
WrappedFile(File(path))
|
File(JavaFile(path))
|
||||||
|
|
||||||
fun from(path: FilePath) =
|
fun from(path: FilePath) =
|
||||||
from(path.path)
|
from(path.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmInline
|
// TODO: Move to value class when mocking them with mockk works
|
||||||
value class FilePath(val path: String)
|
class FilePath(val path: String)
|
||||||
|
|
||||||
/** Runs the given [block] in the context of a file with the given [fullPath]. */
|
/** Runs the given [block] in the context of a file with the given [fullPath]. */
|
||||||
fun <T> withFileAt(fullPath: FilePath, block: WrappedFile.() -> T) =
|
fun <T> withFileAt(fullPath: FilePath, block: File.() -> T) =
|
||||||
WrappedFile.from(fullPath).block()
|
File.from(fullPath).block()
|
||||||
|
|
||||||
|
/** Shortcut to create a file and its parent directories. */
|
||||||
|
fun JavaFile.create() {
|
||||||
|
Files.createDirectories(this.parentFile.toPath())
|
||||||
|
Files.createFile(this.toPath())
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
package dev.fyloz.colorrecipesexplorer.service.files
|
package dev.fyloz.colorrecipesexplorer.service.files
|
||||||
|
|
||||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
||||||
import dev.fyloz.colorrecipesexplorer.utils.WrappedFile
|
import dev.fyloz.colorrecipesexplorer.utils.File
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import org.springframework.mock.web.MockMultipartFile
|
import org.springframework.mock.web.MockMultipartFile
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
@ -21,54 +21,121 @@ private const val mockFilePath = "existingFile"
|
||||||
private val mockFilePathPath = Path.of(mockFilePath)
|
private val mockFilePathPath = Path.of(mockFilePath)
|
||||||
private val mockFileData = byteArrayOf(0x1, 0x8, 0xa, 0xf)
|
private val mockFileData = byteArrayOf(0x1, 0x8, 0xa, 0xf)
|
||||||
|
|
||||||
private class FileServiceTestContext {
|
class FileServiceTest {
|
||||||
val fileService = spyk(FileServiceImpl(creProperties, mockk {
|
private val fileService = spyk(FileServiceImpl(creProperties, mockk {
|
||||||
every { error(any(), any<Exception>()) } just Runs
|
every { error(any(), any<Exception>()) } just Runs
|
||||||
}))
|
}))
|
||||||
val mockFile = mockk<WrappedFile> {
|
private val mockFile = mockk<File> {
|
||||||
every { file } returns mockk()
|
every { file } returns mockk()
|
||||||
every { exists() } returns true
|
every { exists() } returns true
|
||||||
every { isFile } returns true
|
every { isFile } returns true
|
||||||
every { toPath() } returns mockFilePathPath
|
every { toPath() } returns mockFilePathPath
|
||||||
}
|
}
|
||||||
val mockMultipartFile = spyk(MockMultipartFile(mockFilePath, mockFileData))
|
private val mockMultipartFile = spyk(MockMultipartFile(mockFilePath, mockFileData))
|
||||||
|
|
||||||
init {
|
@BeforeEach
|
||||||
mockkObject(WrappedFile.Companion)
|
internal fun beforeEach() {
|
||||||
every { WrappedFile.from(any<String>()) } returns mockFile
|
mockkObject(File.Companion)
|
||||||
|
every { File.from(any<String>()) } returns mockFile
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class FileServiceTest {
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
internal fun afterEach() {
|
internal fun afterEach() {
|
||||||
clearAllMocks()
|
clearAllMocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun whenFileCached(cached: Boolean = true, test: () -> Unit) {
|
||||||
|
mockkObject(FileExistCache) {
|
||||||
|
every { FileExistCache.contains(any()) } returns cached
|
||||||
|
|
||||||
|
test()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun whenFileNotCached(test: () -> Unit) {
|
||||||
|
whenFileCached(false, test)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun whenMockFilePathExists(exists: Boolean = true, test: () -> Unit) {
|
||||||
|
every { fileService.exists(mockFilePath) } returns exists
|
||||||
|
test()
|
||||||
|
}
|
||||||
|
|
||||||
// exists()
|
// exists()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `exists() returns true when the file at the given path exists and is a file`() {
|
fun `exists() returns true when the file at the given path exists and is a file`() {
|
||||||
test {
|
whenFileNotCached {
|
||||||
assertTrue { fileService.exists(mockFilePath) }
|
assertTrue { fileService.exists(mockFilePath) }
|
||||||
|
|
||||||
|
verify {
|
||||||
|
mockFile.exists()
|
||||||
|
mockFile.isFile
|
||||||
|
}
|
||||||
|
confirmVerified(mockFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `exists() returns false when the file at the given path does not exist`() {
|
fun `exists() returns false when the file at the given path does not exist`() {
|
||||||
test {
|
whenFileNotCached {
|
||||||
every { mockFile.exists() } returns false
|
every { mockFile.exists() } returns false
|
||||||
|
|
||||||
assertFalse { fileService.exists(mockFilePath) }
|
assertFalse { fileService.exists(mockFilePath) }
|
||||||
|
|
||||||
|
verify {
|
||||||
|
mockFile.exists()
|
||||||
|
}
|
||||||
|
confirmVerified(mockFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `exists() returns false when the file at the given path is not a file`() {
|
fun `exists() returns false when the file at the given path is not a file`() {
|
||||||
test {
|
whenFileNotCached {
|
||||||
every { mockFile.isFile } returns false
|
every { mockFile.isFile } returns false
|
||||||
|
|
||||||
assertFalse { fileService.exists(mockFilePath) }
|
assertFalse { fileService.exists(mockFilePath) }
|
||||||
|
|
||||||
|
verify {
|
||||||
|
mockFile.exists()
|
||||||
|
mockFile.isFile
|
||||||
|
}
|
||||||
|
confirmVerified(mockFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `exists() returns true when the file at the given path is cached as existing`() {
|
||||||
|
whenFileCached {
|
||||||
|
every { FileExistCache.exists(any()) } returns true
|
||||||
|
|
||||||
|
assertTrue { fileService.exists(mockFilePath) }
|
||||||
|
|
||||||
|
verify {
|
||||||
|
FileExistCache.contains(any())
|
||||||
|
FileExistCache.exists(any())
|
||||||
|
|
||||||
|
mockFile wasNot called
|
||||||
|
}
|
||||||
|
confirmVerified(FileExistCache, mockFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `exists() returns false when the file at the given path is cached as not existing`() {
|
||||||
|
whenFileCached {
|
||||||
|
every { FileExistCache.exists(any()) } returns false
|
||||||
|
|
||||||
|
assertFalse { fileService.exists(mockFilePath) }
|
||||||
|
|
||||||
|
verify {
|
||||||
|
FileExistCache.contains(any())
|
||||||
|
FileExistCache.exists(any())
|
||||||
|
|
||||||
|
mockFile wasNot called
|
||||||
|
}
|
||||||
|
confirmVerified(FileExistCache, mockFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,39 +143,33 @@ class FileServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `read() returns a valid ByteArrayResource`() {
|
fun `read() returns a valid ByteArrayResource`() {
|
||||||
test {
|
whenMockFilePathExists {
|
||||||
whenMockFilePathExists {
|
mockkStatic(File::readBytes)
|
||||||
mockkStatic(File::readBytes)
|
every { mockFile.readBytes() } returns mockFileData
|
||||||
every { mockFile.readBytes() } returns mockFileData
|
|
||||||
|
|
||||||
val redResource = fileService.read(mockFilePath)
|
val redResource = fileService.read(mockFilePath)
|
||||||
|
|
||||||
assertEquals(mockFileData, redResource.byteArray)
|
assertEquals(mockFileData, redResource.byteArray)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `read() throws FileNotFoundException when no file exists at the given path`() {
|
fun `read() throws FileNotFoundException when no file exists at the given path`() {
|
||||||
test {
|
whenMockFilePathExists(false) {
|
||||||
whenMockFilePathExists(false) {
|
with(assertThrows<FileNotFoundException> { fileService.read(mockFilePath) }) {
|
||||||
with(assertThrows<FileNotFoundException> { fileService.read(mockFilePath) }) {
|
assertEquals(mockFilePath, this.path)
|
||||||
assertEquals(mockFilePath, this.path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `read() throws FileReadException when an IOException is thrown`() {
|
fun `read() throws FileReadException when an IOException is thrown`() {
|
||||||
test {
|
whenMockFilePathExists {
|
||||||
whenMockFilePathExists {
|
mockkStatic(File::readBytes)
|
||||||
mockkStatic(File::readBytes)
|
every { mockFile.readBytes() } throws IOException()
|
||||||
every { mockFile.readBytes() } throws IOException()
|
|
||||||
|
|
||||||
with(assertThrows<FileReadException> { fileService.read(mockFilePath) }) {
|
with(assertThrows<FileReadException> { fileService.read(mockFilePath) }) {
|
||||||
assertEquals(mockFilePath, this.path)
|
assertEquals(mockFilePath, this.path)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,15 +178,20 @@ class FileServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `create() creates a file at the given path`() {
|
fun `create() creates a file at the given path`() {
|
||||||
test {
|
whenMockFilePathExists(false) {
|
||||||
whenMockFilePathExists(false) {
|
whenFileNotCached {
|
||||||
mockkStatic(File::create)
|
mockkStatic(File::create) {
|
||||||
every { mockFile.create() } just Runs
|
every { mockFile.create() } just Runs
|
||||||
|
every { FileExistCache.setExists(any()) } just Runs
|
||||||
|
|
||||||
fileService.create(mockFilePath)
|
fileService.create(mockFilePath)
|
||||||
|
|
||||||
verify {
|
verify {
|
||||||
mockFile.create()
|
mockFile.create()
|
||||||
|
|
||||||
|
FileExistCache.setExists(any())
|
||||||
|
}
|
||||||
|
confirmVerified(mockFile, FileExistCache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,27 +199,23 @@ class FileServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `create() does nothing when a file already exists at the given path`() {
|
fun `create() does nothing when a file already exists at the given path`() {
|
||||||
test {
|
whenMockFilePathExists {
|
||||||
whenMockFilePathExists {
|
fileService.create(mockFilePath)
|
||||||
fileService.create(mockFilePath)
|
|
||||||
|
|
||||||
verify(exactly = 0) {
|
verify(exactly = 0) {
|
||||||
mockFile.create()
|
mockFile.create()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `create() throws FileCreateException when the file creation throws an IOException`() {
|
fun `create() throws FileCreateException when the file creation throws an IOException`() {
|
||||||
test {
|
whenMockFilePathExists(false) {
|
||||||
whenMockFilePathExists(false) {
|
mockkStatic(File::create)
|
||||||
mockkStatic(File::create)
|
every { mockFile.create() } throws IOException()
|
||||||
every { mockFile.create() } throws IOException()
|
|
||||||
|
|
||||||
with(assertThrows<FileCreateException> { fileService.create(mockFilePath) }) {
|
with(assertThrows<FileCreateException> { fileService.create(mockFilePath) }) {
|
||||||
assertEquals(mockFilePath, this.path)
|
assertEquals(mockFilePath, this.path)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,59 +224,51 @@ class FileServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `write() creates and writes the given MultipartFile to the file at the given path`() {
|
fun `write() creates and writes the given MultipartFile to the file at the given path`() {
|
||||||
test {
|
whenMockFilePathExists(false) {
|
||||||
whenMockFilePathExists(false) {
|
every { fileService.create(mockFilePath) } just Runs
|
||||||
every { fileService.create(mockFilePath) } just Runs
|
every { mockMultipartFile.transferTo(mockFilePathPath) } just Runs
|
||||||
every { mockMultipartFile.transferTo(mockFilePathPath) } just Runs
|
|
||||||
|
|
||||||
fileService.write(mockMultipartFile, mockFilePath, false)
|
fileService.write(mockMultipartFile, mockFilePath, false)
|
||||||
|
|
||||||
verify {
|
verify {
|
||||||
fileService.create(mockFilePath)
|
fileService.create(mockFilePath)
|
||||||
mockMultipartFile.transferTo(mockFilePathPath)
|
mockMultipartFile.transferTo(mockFilePathPath)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `write() throws FileExistsException when a file at the given path already exists and overwrite is disabled`() {
|
fun `write() throws FileExistsException when a file at the given path already exists and overwrite is disabled`() {
|
||||||
test {
|
whenMockFilePathExists {
|
||||||
whenMockFilePathExists {
|
with(assertThrows<FileExistsException> { fileService.write(mockMultipartFile, mockFilePath, false) }) {
|
||||||
with(assertThrows<FileExistsException> { fileService.write(mockMultipartFile, mockFilePath, false) }) {
|
assertEquals(mockFilePath, this.path)
|
||||||
assertEquals(mockFilePath, this.path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `write() writes the given MultipartFile to an existing file when overwrite is enabled`() {
|
fun `write() writes the given MultipartFile to an existing file when overwrite is enabled`() {
|
||||||
test {
|
whenMockFilePathExists {
|
||||||
whenMockFilePathExists {
|
every { mockMultipartFile.transferTo(mockFilePathPath) } just Runs
|
||||||
every { mockMultipartFile.transferTo(mockFilePathPath) } just Runs
|
|
||||||
|
|
||||||
fileService.write(mockMultipartFile, mockFilePath, true)
|
fileService.write(mockMultipartFile, mockFilePath, true)
|
||||||
|
|
||||||
verify {
|
verify {
|
||||||
mockMultipartFile.transferTo(mockFilePathPath)
|
mockMultipartFile.transferTo(mockFilePathPath)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `write() throws FileWriteException when writing the given file throws an IOException`() {
|
fun `write() throws FileWriteException when writing the given file throws an IOException`() {
|
||||||
test {
|
whenMockFilePathExists(false) {
|
||||||
whenMockFilePathExists(false) {
|
every { fileService.create(mockFilePath) } just Runs
|
||||||
every { fileService.create(mockFilePath) } just Runs
|
every { mockMultipartFile.transferTo(mockFilePathPath) } throws IOException()
|
||||||
every { mockMultipartFile.transferTo(mockFilePathPath) } throws IOException()
|
|
||||||
|
|
||||||
with(assertThrows<FileWriteException> {
|
with(assertThrows<FileWriteException> {
|
||||||
fileService.write(mockMultipartFile, mockFilePath, false)
|
fileService.write(mockMultipartFile, mockFilePath, false)
|
||||||
}) {
|
}) {
|
||||||
assertEquals(mockFilePath, this.path)
|
assertEquals(mockFilePath, this.path)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,35 +277,39 @@ class FileServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `delete() deletes the file at the given path`() {
|
fun `delete() deletes the file at the given path`() {
|
||||||
test {
|
whenMockFilePathExists {
|
||||||
whenMockFilePathExists {
|
whenFileCached {
|
||||||
every { mockFile.delete() } returns true
|
every { mockFile.delete() } returns true
|
||||||
|
every { FileExistCache.setDoesNotExists(any()) } just Runs
|
||||||
|
|
||||||
fileService.delete(mockFilePath)
|
fileService.delete(mockFilePath)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
mockFile.delete()
|
||||||
|
|
||||||
|
FileExistCache.setDoesNotExists(any())
|
||||||
|
}
|
||||||
|
confirmVerified(mockFile, FileExistCache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `delete() throws FileNotFoundException when no file exists at the given path`() {
|
fun `delete() throws FileNotFoundException when no file exists at the given path`() {
|
||||||
test {
|
whenMockFilePathExists(false) {
|
||||||
whenMockFilePathExists(false) {
|
with(assertThrows<FileNotFoundException> { fileService.delete(mockFilePath) }) {
|
||||||
with(assertThrows<FileNotFoundException> { fileService.delete(mockFilePath) }) {
|
assertEquals(mockFilePath, this.path)
|
||||||
assertEquals(mockFilePath, this.path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `delete() throws FileDeleteException when deleting throw and IOException`() {
|
fun `delete() throws FileDeleteException when deleting throw and IOException`() {
|
||||||
test {
|
whenMockFilePathExists {
|
||||||
whenMockFilePathExists {
|
every { mockFile.delete() } throws IOException()
|
||||||
every { mockFile.delete() } throws IOException()
|
|
||||||
|
|
||||||
with(assertThrows<FileDeleteException> { fileService.delete(mockFilePath) }) {
|
with(assertThrows<FileDeleteException> { fileService.delete(mockFilePath) }) {
|
||||||
assertEquals(mockFilePath, this.path)
|
assertEquals(mockFilePath, this.path)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,37 +318,24 @@ class FileServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `fullPath() appends the given path to the given working directory`() {
|
fun `fullPath() appends the given path to the given working directory`() {
|
||||||
test {
|
with(fileService) {
|
||||||
with(fileService) {
|
val fullFilePath = mockFilePath.fullPath()
|
||||||
val fullFilePath = mockFilePath.fullPath()
|
|
||||||
|
|
||||||
assertEquals("${creProperties.dataDirectory}/$mockFilePath", fullFilePath.path)
|
assertEquals("${creProperties.dataDirectory}/$mockFilePath", fullFilePath.path)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `fullPath() throws InvalidFilePathException when the given path contains invalid fragments`() {
|
fun `fullPath() throws InvalidFilePathException when the given path contains invalid fragments`() {
|
||||||
test {
|
with(fileService) {
|
||||||
with(fileService) {
|
BANNED_FILE_PATH_SHARDS.forEach {
|
||||||
BANNED_FILE_PATH_SHARDS.forEach {
|
val maliciousPath = "$it/$mockFilePath"
|
||||||
val maliciousPath = "$it/$mockFilePath"
|
|
||||||
|
|
||||||
with(assertThrows<InvalidFilePathException> { maliciousPath.fullPath() }) {
|
with(assertThrows<InvalidFilePathException> { maliciousPath.fullPath() }) {
|
||||||
assertEquals(maliciousPath, this.path)
|
assertEquals(maliciousPath, this.path)
|
||||||
assertEquals(it, this.fragment)
|
assertEquals(it, this.fragment)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun test(test: FileServiceTestContext.() -> Unit) {
|
|
||||||
FileServiceTestContext().test()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun FileServiceTestContext.whenMockFilePathExists(exists: Boolean = true, test: () -> Unit) {
|
|
||||||
every { fileService.exists(mockFilePath) } returns exists
|
|
||||||
test()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue