Compare commits

..

2 Commits

Author SHA1 Message Date
FyloZ e6b1ba3b45
#18 Add FileCache
continuous-integration/drone/push Build is passing Details
2022-01-03 13:44:23 -05:00
FyloZ 26d696d66b
#18 Add logging to cache 2022-01-01 18:01:59 -05:00
12 changed files with 323 additions and 137 deletions

View File

@ -102,7 +102,7 @@ tasks.withType<JavaCompile>() {
}
tasks.withType<KotlinCompile>().all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
jvmTarget = JavaVersion.VERSION_17.toString()
freeCompilerArgs = listOf(
"-Xopt-in=kotlin.contracts.ExperimentalContracts",
"-Xinline-classes"

View File

@ -51,7 +51,7 @@ class MaterialTypeInitializer(
// Remove old system types
oldSystemTypes.forEach {
logger.info("Material type '${it.name}' is not a system type anymore")
materialTypeService.update(materialType(it, newSystemType = false))
materialTypeService.updateSystemType(it.copy(systemType = false))
}
}
}

View File

@ -7,7 +7,7 @@ import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Service
interface MaterialTypeService :
ExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository> {
ExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository> {
/** Checks if a material type with the given [prefix] exists. */
fun existsByPrefix(prefix: String): Boolean
@ -19,14 +19,17 @@ interface MaterialTypeService :
/** Gets all material types who are not a system type. */
fun getAllNonSystemType(): Collection<MaterialType>
/** Allows to update the given system [materialType], should not be exposed to users. */
fun updateSystemType(materialType: MaterialType): MaterialType
}
@Service
@Profile("!emergency")
class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val materialService: MaterialService) :
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository>(
repository
), MaterialTypeService {
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository>(
repository
), MaterialTypeService {
override fun idNotFoundException(id: Long) = materialTypeIdNotFoundException(id)
override fun idAlreadyExistsException(id: Long) = materialIdAlreadyExistsException(id)
override fun nameNotFoundException(name: String) = materialTypeNameNotFoundException(name)
@ -36,7 +39,7 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
override fun existsByPrefix(prefix: String): Boolean = repository.existsByPrefix(prefix)
override fun isUsedByMaterial(materialType: MaterialType): Boolean =
materialService.existsByMaterialType(materialType)
materialService.existsByMaterialType(materialType)
override fun getAllSystemTypes(): Collection<MaterialType> = repository.findAllBySystemTypeIs(true)
override fun getAllNonSystemType(): Collection<MaterialType> = repository.findAllBySystemTypeIs(false)
@ -52,16 +55,22 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
return update(with(entity) {
MaterialType(
id = id,
name = if (isNotNullAndNotBlank(name)) name else persistedMaterialType.name,
prefix = if (isNotNullAndNotBlank(prefix)) prefix else persistedMaterialType.prefix,
systemType = false
id = id,
name = if (isNotNullAndNotBlank(name)) name else persistedMaterialType.name,
prefix = if (isNotNullAndNotBlank(prefix)) prefix else persistedMaterialType.prefix,
systemType = false
)
})
}
override fun update(entity: MaterialType): MaterialType {
if (repository.existsByIdAndSystemTypeIsTrue(entity.id!!)) {
override fun updateSystemType(materialType: MaterialType) =
update(materialType, true)
override fun update(entity: MaterialType) =
update(entity, false)
private fun update(entity: MaterialType, allowSystemTypes: Boolean): MaterialType {
if (!allowSystemTypes && repository.existsByIdAndSystemTypeIsTrue(entity.id!!)) {
throw cannotUpdateSystemMaterialTypeException(entity)
}

View File

@ -1,5 +1,6 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.model.account.Group
import dev.fyloz.colorrecipesexplorer.model.validation.or
@ -9,10 +10,8 @@ import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
import dev.fyloz.colorrecipesexplorer.service.users.GroupService
import dev.fyloz.colorrecipesexplorer.utils.setAll
import org.springframework.context.annotation.Lazy
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import java.io.File
import java.time.LocalDate
import java.time.Period
import javax.transaction.Transactional
@ -45,7 +44,7 @@ interface RecipeService :
}
@Service
@Profile("!emergency")
@RequireDatabase
class RecipeServiceImpl(
recipeRepository: RecipeRepository,
val companyService: CompanyService,
@ -213,29 +212,20 @@ interface RecipeImageService {
/** Deletes the image with the given [name] for the given [recipe]. */
fun delete(recipe: Recipe, name: String)
/** Gets the directory containing all images of the given [Recipe]. */
fun Recipe.getDirectory(): File
}
const val RECIPE_IMAGE_ID_DELIMITER = "_"
const val RECIPE_IMAGE_EXTENSION = ".jpg"
@Service
@Profile("!emergency")
@RequireDatabase
class RecipeImageServiceImpl(
val fileService: WriteableFileService
) : RecipeImageService {
override fun getAllImages(recipe: Recipe): Set<String> {
val recipeDirectory = recipe.getDirectory()
if (!recipeDirectory.exists() || !recipeDirectory.isDirectory) {
return setOf()
}
return recipeDirectory.listFiles()!! // Should never be null because we check if recipeDirectory exists and is a directory before
.filterNotNull()
override fun getAllImages(recipe: Recipe) =
fileService.listDirectoryFiles(recipe.imagesDirectoryPath)
.map { it.name }
.toSet()
}
override fun download(image: MultipartFile, recipe: Recipe): String {
/** Gets the next id available for a new image for the given [recipe]. */
@ -252,17 +242,15 @@ class RecipeImageServiceImpl(
} + 1L
}
return getImageFileName(recipe, getNextAvailableId()).apply {
fileService.write(image, getImagePath(recipe, this), true)
return getImageFileName(recipe, getNextAvailableId()).also {
with(getImagePath(recipe, it)) {
fileService.writeToDirectory(image, this, recipe.imagesDirectoryPath, true)
}
}
}
override fun delete(recipe: Recipe, name: String) =
fileService.delete(getImagePath(recipe, name))
override fun Recipe.getDirectory(): File = File(with(fileService) {
this@getDirectory.imagesDirectoryPath.fullPath().path
})
fileService.deleteFromDirectory(getImagePath(recipe, name), recipe.imagesDirectoryPath)
private fun getImageFileName(recipe: Recipe, id: Long) =
"${recipe.name}$RECIPE_IMAGE_ID_DELIMITER$id"

View File

@ -0,0 +1,150 @@
package dev.fyloz.colorrecipesexplorer.service.files
import dev.fyloz.colorrecipesexplorer.JavaFile
import dev.fyloz.colorrecipesexplorer.utils.File
import dev.fyloz.colorrecipesexplorer.utils.FilePath
import mu.KotlinLogging
object FileCache {
private val logger = KotlinLogging.logger {}
private val cache = hashMapOf<String, CachedFileSystemItem>()
operator fun contains(filePath: FilePath) =
filePath.path in cache
operator fun get(filePath: FilePath) =
cache[filePath.path]
fun getDirectory(filePath: FilePath) =
if (directoryExists(filePath)) {
this[filePath] as CachedDirectory
} else {
null
}
fun getFile(filePath: FilePath) =
if (fileExists(filePath)) {
this[filePath] as CachedFile
} else {
null
}
private operator fun set(filePath: FilePath, item: CachedFileSystemItem) {
cache[filePath.path] = item
}
fun exists(filePath: FilePath) =
filePath in this && cache[filePath.path]!!.exists
fun directoryExists(filePath: FilePath) =
exists(filePath) && this[filePath] is CachedDirectory
fun fileExists(filePath: FilePath) =
exists(filePath) && this[filePath] is CachedFile
fun setExists(filePath: FilePath, exists: Boolean = true) {
if (filePath !in this) {
load(filePath)
}
this[filePath] = this[filePath]!!.clone(exists)
logger.debug("Updated FileCache state: ${filePath.path} exists -> $exists")
}
fun setDoesNotExists(filePath: FilePath) =
setExists(filePath, false)
fun load(filePath: FilePath) =
with(JavaFile(filePath.path).toFileSystemItem()) {
cache[filePath.path] = this
logger.debug("Loaded file at ${filePath.path} into FileCache")
}
fun addContent(filePath: FilePath, childFilePath: FilePath) {
val directory = prepareDirectory(filePath) ?: return
val updatedContent = setOf(
*directory.content.toTypedArray(),
JavaFile(childFilePath.path).toFileSystemItem()
)
this[filePath] = directory.copy(content = updatedContent)
logger.debug("Added child ${childFilePath.path} to ${filePath.path} in FileCache")
}
fun removeContent(filePath: FilePath, childFilePath: FilePath) {
val directory = prepareDirectory(filePath) ?: return
val updatedContent = directory.content
.filter { it.path.path != childFilePath.path }
.toSet()
this[filePath] = directory.copy(content = updatedContent)
logger.debug("Removed child ${childFilePath.path} from ${filePath.path} in FileCache")
}
private fun prepareDirectory(filePath: FilePath): CachedDirectory? {
if (!directoryExists(filePath)) {
logger.warn("Cannot add child to ${filePath.path} because it is not in the cache")
return null
}
val directory = getDirectory(filePath)
if (directory == null) {
logger.warn("Cannot add child to ${filePath.path} because it is not a directory")
return null
}
return directory
}
}
interface CachedFileSystemItem {
val name: String
val path: FilePath
val exists: Boolean
fun clone(exists: Boolean): CachedFileSystemItem
}
data class CachedFile(
override val name: String,
override val path: FilePath,
override val exists: Boolean
) : CachedFileSystemItem {
constructor(file: File) : this(file.name, file.toFilePath(), file.exists() && file.isFile)
override fun clone(exists: Boolean) =
this.copy(exists = exists)
}
data class CachedDirectory(
override val name: String,
override val path: FilePath,
override val exists: Boolean,
val content: Set<CachedFileSystemItem> = setOf()
) : CachedFileSystemItem {
constructor(file: File) : this(file.name, file.toFilePath(), file.exists() && file.isDirectory, file.fetchContent())
val contentFiles: Collection<CachedFile>
get() = content.filterIsInstance<CachedFile>()
override fun clone(exists: Boolean) =
this.copy(exists = exists)
companion object {
private fun File.fetchContent() =
(this.file.listFiles() ?: arrayOf<JavaFile>())
.filterNotNull()
.map { it.toFileSystemItem() }
.toSet()
}
}
fun JavaFile.toFileSystemItem() =
if (this.isDirectory) {
CachedDirectory(File(this))
} else {
CachedFile(File(this))
}

View File

@ -1,27 +0,0 @@
package dev.fyloz.colorrecipesexplorer.service.files
import dev.fyloz.colorrecipesexplorer.utils.FilePath
object FileExistCache {
private val map = hashMapOf<FilePath, Boolean>()
/** Checks if the given [path] is in the cache. */
operator fun contains(path: FilePath) =
path in map
/** Checks if the file at the given [path] exists. */
fun exists(path: FilePath) =
map[path] ?: false
/** Sets the file at the given [path] as existing. */
fun setExists(path: FilePath) =
set(path, true)
/** Sets the file at the given [path] as not existing. */
fun setDoesNotExists(path: FilePath) =
set(path, false)
private fun set(path: FilePath, exists: Boolean) {
map[path] = exists
}
}

View File

@ -5,6 +5,7 @@ import dev.fyloz.colorrecipesexplorer.exception.RestException
import dev.fyloz.colorrecipesexplorer.utils.File
import dev.fyloz.colorrecipesexplorer.utils.FilePath
import dev.fyloz.colorrecipesexplorer.utils.withFileAt
import mu.KotlinLogging
import org.slf4j.Logger
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.Resource
@ -27,8 +28,11 @@ interface FileService {
/** Reads the file at the given [path]. */
fun read(path: String): Resource
/** List the files contained in the folder at the given [path]. Returns an empty collection if the directory does not exist. */
fun listDirectoryFiles(path: String): Collection<CachedFile>
/** Completes the path of the given [String] by adding the working directory. */
fun String.fullPath(): FilePath
fun fullPath(path: String): FilePath
}
interface WriteableFileService : FileService {
@ -41,28 +45,37 @@ interface WriteableFileService : FileService {
/** Writes the given [data] to the given [path]. If the file at the path already exists, it will be overwritten if [overwrite] is enabled. */
fun write(data: ByteArrayResource, path: String, overwrite: Boolean)
/** Writes the given [data] to the given [path], and specify the [parentPath]. If the file at the path already exists, it will be overwritten if [overwrite] is enabled. */
fun writeToDirectory(data: MultipartFile, path: String, parentPath: String, overwrite: Boolean)
/** Deletes the file at the given [path]. */
fun delete(path: String)
/** Deletes the file at the given [path], and specify the [parentPath]. */
fun deleteFromDirectory(path: String, parentPath: String)
}
@Service
class FileServiceImpl(
private val creProperties: CreProperties,
private val logger: Logger
private val creProperties: CreProperties
) : WriteableFileService {
private val logger = KotlinLogging.logger {}
override fun exists(path: String): Boolean {
val fullPath = path.fullPath()
return if (fullPath in FileExistCache) {
FileExistCache.exists(fullPath)
val fullPath = fullPath(path)
return if (fullPath in FileCache) {
FileCache.exists(fullPath)
} else {
withFileAt(fullPath) {
this.exists() && this.isFile
(this.exists() && this.isFile).also {
FileCache.setExists(fullPath, it)
}
}
}
}
override fun read(path: String) = ByteArrayResource(
withFileAt(path.fullPath()) {
withFileAt(fullPath(path)) {
if (!exists(path)) throw FileNotFoundException(path)
try {
readBytes()
@ -72,13 +85,25 @@ class FileServiceImpl(
}
)
override fun listDirectoryFiles(path: String): Collection<CachedFile> =
with(fullPath(path)) {
if (this !in FileCache) {
FileCache.load(this)
}
(FileCache.getDirectory(this) ?: return setOf())
.contentFiles
}
override fun create(path: String) {
val fullPath = path.fullPath()
val fullPath = fullPath(path)
if (!exists(path)) {
try {
withFileAt(fullPath) {
this.create()
FileExistCache.setExists(fullPath)
FileCache.setExists(fullPath)
logger.info("Created file at '${fullPath.path}'")
}
} catch (ex: IOException) {
FileCreateException(path).logAndThrow(ex, logger)
@ -88,38 +113,52 @@ class FileServiceImpl(
override fun write(file: MultipartFile, path: String, overwrite: Boolean) =
prepareWrite(path, overwrite) {
logWrittenDataSize(file.size)
file.transferTo(this.toPath())
}
override fun write(data: ByteArrayResource, path: String, overwrite: Boolean) =
prepareWrite(path, overwrite) {
logWrittenDataSize(data.contentLength())
this.writeBytes(data.byteArray)
}
override fun writeToDirectory(data: MultipartFile, path: String, parentPath: String, overwrite: Boolean) {
FileCache.addContent(fullPath(parentPath), fullPath(path))
write(data, path, overwrite)
}
override fun delete(path: String) {
try {
val fullPath = path.fullPath()
val fullPath = fullPath(path)
withFileAt(fullPath) {
if (!exists(path)) throw FileNotFoundException(path)
this.delete()
FileExistCache.setDoesNotExists(fullPath)
FileCache.setDoesNotExists(fullPath)
logger.info("Deleted file at '${fullPath.path}'")
}
} catch (ex: IOException) {
FileDeleteException(path).logAndThrow(ex, logger)
}
}
override fun String.fullPath(): FilePath {
BANNED_FILE_PATH_SHARDS
.firstOrNull { this.contains(it) }
?.let { throw InvalidFilePathException(this, it) }
override fun deleteFromDirectory(path: String, parentPath: String) {
FileCache.removeContent(fullPath(parentPath), fullPath(path))
delete(path)
}
return FilePath("${creProperties.dataDirectory}/$this")
override fun fullPath(path: String): FilePath {
BANNED_FILE_PATH_SHARDS
.firstOrNull { path.contains(it) }
?.let { throw InvalidFilePathException(path, it) }
return FilePath("${creProperties.dataDirectory}/$path")
}
private fun prepareWrite(path: String, overwrite: Boolean, op: File.() -> Unit) {
val fullPath = path.fullPath()
val fullPath = fullPath(path)
if (exists(path)) {
if (!overwrite) throw FileExistsException(path)
@ -130,11 +169,17 @@ class FileServiceImpl(
try {
withFileAt(fullPath) {
this.op()
logger.info("Wrote data to file at '${fullPath.path}'")
}
} catch (ex: IOException) {
FileWriteException(path).logAndThrow(ex, logger)
}
}
private fun logWrittenDataSize(size: Long) {
logger.debug("Writing $size bytes to file system...")
}
}
private const val FILE_IO_EXCEPTION_TITLE = "File IO error"

View File

@ -10,17 +10,26 @@ class ResourceFileService(
private val resourceLoader: ResourceLoader
) : FileService {
override fun exists(path: String) =
path.fullPath().resource.exists()
fullPath(path).resource.exists()
override fun read(path: String): Resource =
path.fullPath().resource.also {
fullPath(path).resource.also {
if (!it.exists()) {
throw FileNotFoundException(path)
}
}
override fun String.fullPath() =
FilePath("classpath:${this}")
override fun listDirectoryFiles(path: String): Collection<CachedFile> {
val content = fullPath(path).resource.file.listFiles() ?: return setOf()
return content
.filterNotNull()
.filter { it.isFile }
.map { it.toFileSystemItem() as CachedFile }
}
override fun fullPath(path: String) =
FilePath("classpath:${path}")
val FilePath.resource: Resource
get() = resourceLoader.getResource(this.path)

View File

@ -6,33 +6,42 @@ import java.nio.file.Path
/** Mockable file wrapper, to prevent issues when mocking [java.io.File]. */
class File(val file: JavaFile) {
val name: String
get() = file.name
val isFile: Boolean
get() = file.isFile
val isDirectory: Boolean
get() = file.isDirectory
fun toPath(): Path =
file.toPath()
file.toPath()
fun toFilePath(): FilePath =
FilePath(file.path)
fun exists() =
file.exists()
file.exists()
fun readBytes() =
file.readBytes()
file.readBytes()
fun writeBytes(array: ByteArray) =
file.writeBytes(array)
file.writeBytes(array)
fun create() =
file.create()
file.create()
fun delete(): Boolean =
file.delete()
file.delete()
companion object {
fun from(path: String) =
File(JavaFile(path))
File(JavaFile(path))
fun from(path: FilePath) =
from(path.path)
from(path.path)
}
}
@ -41,7 +50,7 @@ class FilePath(val path: String)
/** Runs the given [block] in the context of a file with the given [fullPath]. */
fun <T> withFileAt(fullPath: FilePath, block: File.() -> T) =
File.from(fullPath).block()
File.from(fullPath).block()
/** Shortcut to create a file and its parent directories. */
fun JavaFile.create() {

View File

@ -6,8 +6,10 @@ import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.model.account.group
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService
import dev.fyloz.colorrecipesexplorer.service.files.CachedFile
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
import dev.fyloz.colorrecipesexplorer.service.users.GroupService
import dev.fyloz.colorrecipesexplorer.utils.FilePath
import io.mockk.*
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
@ -15,7 +17,6 @@ import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.assertThrows
import org.springframework.mock.web.MockMultipartFile
import org.springframework.web.multipart.MultipartFile
import java.io.File
import java.time.LocalDate
import java.time.Period
import kotlin.test.*
@ -30,7 +31,17 @@ class RecipeServiceTest :
private val recipeStepService: RecipeStepService = mock()
private val configService: ConfigurationService = mock()
override val service: RecipeService =
spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService, mock(), configService))
spy(
RecipeServiceImpl(
repository,
companyService,
mixService,
recipeStepService,
groupService,
mock(),
configService
)
)
private val company: Company = company(id = 0L)
override val entity: Recipe = recipe(id = 0L, name = "recipe", company = company)
@ -273,18 +284,7 @@ private class RecipeImageServiceTestContext {
val recipe = spyk(recipe())
val recipeImagesIds = setOf(1L, 10L, 21L)
val recipeImagesNames = recipeImagesIds.map { it.imageName }.toSet()
val recipeImagesFiles = recipeImagesNames.map { File(it) }.toTypedArray()
val recipeDirectory = mockk<File> {
every { exists() } returns true
every { isDirectory } returns true
every { listFiles() } returns recipeImagesFiles
}
init {
with(recipeImageService) {
every { recipe.getDirectory() } returns recipeDirectory
}
}
val recipeImagesFiles = recipeImagesNames.map { CachedFile(it, FilePath(it), true) }
val Long.imageName
get() = "${recipe.name}$RECIPE_IMAGE_ID_DELIMITER$this"
@ -308,6 +308,8 @@ class RecipeImageServiceTest {
@Test
fun `getAllImages() returns a Set containing the name of every files in the recipe's directory`() {
test {
every { fileService.listDirectoryFiles(any()) } returns recipeImagesFiles
val foundImagesNames = recipeImageService.getAllImages(recipe)
assertEquals(recipeImagesNames, foundImagesNames)
@ -317,7 +319,7 @@ class RecipeImageServiceTest {
@Test
fun `getAllImages() returns an empty Set when the recipe's directory does not exists`() {
test {
every { recipeDirectory.exists() } returns false
every { fileService.listDirectoryFiles(any()) } returns emptySet()
assertTrue {
recipeImageService.getAllImages(recipe).isEmpty()
@ -335,12 +337,15 @@ class RecipeImageServiceTest {
val expectedImageName = expectedImageId.imageName
val expectedImagePath = expectedImageName.imagePath
every { fileService.listDirectoryFiles(any()) } returns recipeImagesFiles
every { fileService.writeToDirectory(any(), any(), any(), any()) } just runs
val foundImageName = recipeImageService.download(mockImage, recipe)
assertEquals(expectedImageName, foundImageName)
verify {
fileService.write(mockImage, expectedImagePath, true)
fileService.writeToDirectory(mockImage, expectedImagePath, any(), true)
}
}
}
@ -353,10 +358,12 @@ class RecipeImageServiceTest {
val imageName = recipeImagesIds.first().imageName
val imagePath = imageName.imagePath
every { fileService.deleteFromDirectory(any(), any()) } just runs
recipeImageService.delete(recipe, imageName)
verify {
fileService.delete(imagePath)
fileService.deleteFromDirectory(imagePath, any())
}
}
}

View File

@ -22,9 +22,7 @@ private val mockFilePathPath = Path.of(mockFilePath)
private val mockFileData = byteArrayOf(0x1, 0x8, 0xa, 0xf)
class FileServiceTest {
private val fileService = spyk(FileServiceImpl(creProperties, mockk {
every { error(any(), any<Exception>()) } just Runs
}))
private val fileService = spyk(FileServiceImpl(creProperties))
private val mockFile = mockk<File> {
every { file } returns mockk()
every { exists() } returns true
@ -45,8 +43,8 @@ class FileServiceTest {
}
private fun whenFileCached(cached: Boolean = true, test: () -> Unit) {
mockkObject(FileExistCache) {
every { FileExistCache.contains(any()) } returns cached
mockkObject(FileCache) {
every { FileCache.contains(any()) } returns cached
test()
}
@ -108,34 +106,34 @@ class FileServiceTest {
@Test
fun `exists() returns true when the file at the given path is cached as existing`() {
whenFileCached {
every { FileExistCache.exists(any()) } returns true
every { FileCache.exists(any()) } returns true
assertTrue { fileService.exists(mockFilePath) }
verify {
FileExistCache.contains(any())
FileExistCache.exists(any())
FileCache.contains(any())
FileCache.exists(any())
mockFile wasNot called
}
confirmVerified(FileExistCache, mockFile)
confirmVerified(FileCache, 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
every { FileCache.exists(any()) } returns false
assertFalse { fileService.exists(mockFilePath) }
verify {
FileExistCache.contains(any())
FileExistCache.exists(any())
FileCache.contains(any())
FileCache.exists(any())
mockFile wasNot called
}
confirmVerified(FileExistCache, mockFile)
confirmVerified(FileCache, mockFile)
}
}
@ -182,16 +180,16 @@ class FileServiceTest {
whenFileNotCached {
mockkStatic(File::create) {
every { mockFile.create() } just Runs
every { FileExistCache.setExists(any()) } just Runs
every { FileCache.setExists(any()) } just Runs
fileService.create(mockFilePath)
verify {
mockFile.create()
FileExistCache.setExists(any())
FileCache.setExists(any())
}
confirmVerified(mockFile, FileExistCache)
confirmVerified(mockFile, FileCache)
}
}
}
@ -280,16 +278,16 @@ class FileServiceTest {
whenMockFilePathExists {
whenFileCached {
every { mockFile.delete() } returns true
every { FileExistCache.setDoesNotExists(any()) } just Runs
every { FileCache.setDoesNotExists(any()) } just Runs
fileService.delete(mockFilePath)
verify {
mockFile.delete()
FileExistCache.setDoesNotExists(any())
FileCache.setDoesNotExists(any())
}
confirmVerified(mockFile, FileExistCache)
confirmVerified(mockFile, FileCache)
}
}
}
@ -319,7 +317,7 @@ class FileServiceTest {
@Test
fun `fullPath() appends the given path to the given working directory`() {
with(fileService) {
val fullFilePath = mockFilePath.fullPath()
val fullFilePath = fullPath(mockFilePath)
assertEquals("${creProperties.dataDirectory}/$mockFilePath", fullFilePath.path)
}
@ -331,7 +329,7 @@ class FileServiceTest {
BANNED_FILE_PATH_SHARDS.forEach {
val maliciousPath = "$it/$mockFilePath"
with(assertThrows<InvalidFilePathException> { maliciousPath.fullPath() }) {
with(assertThrows<InvalidFilePathException> { fullPath(maliciousPath) }) {
assertEquals(maliciousPath, this.path)
assertEquals(it, this.fragment)
}

View File

@ -27,7 +27,7 @@ class ResourceFileServiceTest {
private fun existsTest(shouldExists: Boolean, test: (String) -> Unit) {
val path = "unit_test_resource"
with(service) {
every { path.fullPath() } returns mockk {
every { fullPath(path) } returns mockk {
every { resource } returns mockk {
every { exists() } returns shouldExists
}
@ -61,7 +61,7 @@ class ResourceFileServiceTest {
}
val path = "unit_test_path"
with(service) {
every { path.fullPath() } returns mockk {
every { fullPath(path) } returns mockk {
every { resource } returns mockResource
}
@ -92,11 +92,9 @@ class ResourceFileServiceTest {
val path = "unit_test_path"
val expectedPath = "classpath:$path"
with(service) {
val found = path.fullPath()
val found = service.fullPath(path)
assertEquals(expectedPath, found.path)
}
assertEquals(expectedPath, found.path)
}
@Test