Add cover loader and refactoring
This commit is contained in:
parent
dc8579674f
commit
c4275e4c81
15 changed files with 262 additions and 95 deletions
|
|
@ -16,11 +16,13 @@ repositories {
|
||||||
dependencies {
|
dependencies {
|
||||||
//implementation("org.eclipse.jgit:org.eclipse.jgit:7.3.0.202506031305-r")
|
//implementation("org.eclipse.jgit:org.eclipse.jgit:7.3.0.202506031305-r")
|
||||||
/// implementation("io.pebbletemplates:pebble:3.2.4")
|
/// implementation("io.pebbletemplates:pebble:3.2.4")
|
||||||
implementation(libs.clikt) // command line argument parsing
|
implementation(libs.clikt)
|
||||||
implementation(libs.kaml) // YAML configuration parsing
|
implementation(libs.kaml)
|
||||||
|
implementation(libs.logging)
|
||||||
|
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
testImplementation(libs.mockk)
|
testImplementation(libs.mockk)
|
||||||
|
testImplementation(libs.junit.jupiter.params)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.test {
|
tasks.test {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,15 @@ shadow = "9.2.2"
|
||||||
clikt = "5.0.3"
|
clikt = "5.0.3"
|
||||||
kaml = "0.97.0"
|
kaml = "0.97.0"
|
||||||
mockk = "1.14.5"
|
mockk = "1.14.5"
|
||||||
|
junit = "5.11.0"
|
||||||
|
logging = "7.0.3"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
clikt = { group = "com.github.ajalt.clikt", name = "clikt", version.ref = "clikt" }
|
clikt = { group = "com.github.ajalt.clikt", name = "clikt", version.ref = "clikt" }
|
||||||
kaml = { group = "com.charleskorn.kaml", name = "kaml", version.ref = "kaml" }
|
kaml = { group = "com.charleskorn.kaml", name = "kaml", version.ref = "kaml" }
|
||||||
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
|
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
|
||||||
|
junit-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" }
|
||||||
|
logging = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "logging" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package eu.m724.bsk2.builder
|
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
// TODO needs a test or two
|
|
||||||
fun Path.isParentOf(child: Path): Boolean =
|
|
||||||
child.toFile().canonicalPath.startsWith(this.toFile().canonicalPath)
|
|
||||||
|
|
||||||
fun Path.contains(other: Path): Boolean =
|
|
||||||
isParentOf(other)
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
package eu.m724.bsk2.builder
|
package eu.m724.bsk2.builder.fs
|
||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
interface FileSystem {
|
interface FileSystem {
|
||||||
fun readText(path: Path): String
|
fun readText(path: Path): String
|
||||||
|
fun exists(path: Path): Boolean
|
||||||
}
|
}
|
||||||
11
src/main/kotlin/eu/m724/bsk2/builder/fs/LocalFileSystem.kt
Normal file
11
src/main/kotlin/eu/m724/bsk2/builder/fs/LocalFileSystem.kt
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
package eu.m724.bsk2.builder.fs
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.exists
|
||||||
|
import kotlin.io.path.readText
|
||||||
|
|
||||||
|
class LocalFileSystem : FileSystem {
|
||||||
|
override fun readText(path: Path): String = path.readText()
|
||||||
|
|
||||||
|
override fun exists(path: Path): Boolean = path.exists()
|
||||||
|
}
|
||||||
38
src/main/kotlin/eu/m724/bsk2/builder/fs/PathUtils.kt
Normal file
38
src/main/kotlin/eu/m724/bsk2/builder/fs/PathUtils.kt
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package eu.m724.bsk2.builder.fs
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
// TODO needs a test or two
|
||||||
|
fun Path.isParentOf(child: Path): Boolean =
|
||||||
|
child.toFile().canonicalPath.startsWith(this.toFile().canonicalPath)
|
||||||
|
|
||||||
|
fun Path.contains(other: Path): Boolean =
|
||||||
|
isParentOf(other)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the given path against this path, ensuring the resulting path is a child of the given path.
|
||||||
|
*
|
||||||
|
* @param other the path to resolve against this path
|
||||||
|
* @return the resulting path
|
||||||
|
* @throws IllegalArgumentException if the resolve results in a traversal
|
||||||
|
*/
|
||||||
|
fun Path.resolveSafe(other: Path): Path {
|
||||||
|
val path = this.resolve(other)
|
||||||
|
|
||||||
|
if (!this.isParentOf(path)) {
|
||||||
|
throw IllegalArgumentException("Path traversal ($path not a child of $this)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the given path against this path, ensuring the resulting path is a child of the given path.
|
||||||
|
*
|
||||||
|
* @param other the path to resolve against this path
|
||||||
|
* @return the resulting path
|
||||||
|
* @throws IllegalArgumentException if the resolve results in a traversal
|
||||||
|
*/
|
||||||
|
fun Path.resolveSafe(other: String): Path {
|
||||||
|
return this.resolveSafe(fileSystem.getPath(other))
|
||||||
|
}
|
||||||
|
|
@ -2,13 +2,13 @@ package eu.m724.bsk2.builder.post.loader
|
||||||
|
|
||||||
import com.charleskorn.kaml.Yaml
|
import com.charleskorn.kaml.Yaml
|
||||||
import com.charleskorn.kaml.YamlConfiguration
|
import com.charleskorn.kaml.YamlConfiguration
|
||||||
import eu.m724.bsk2.builder.FileSystem
|
import eu.m724.bsk2.builder.fs.FileSystem
|
||||||
import eu.m724.bsk2.builder.isParentOf
|
import eu.m724.bsk2.builder.fs.LocalFileSystem
|
||||||
|
import eu.m724.bsk2.builder.fs.resolveSafe
|
||||||
import eu.m724.bsk2.builder.post.data.PostMeta
|
import eu.m724.bsk2.builder.post.data.PostMeta
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.div
|
import kotlin.io.path.div
|
||||||
import kotlin.io.path.readText
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A post loader that loads from a file system
|
* A post loader that loads from a file system
|
||||||
|
|
@ -18,25 +18,41 @@ import kotlin.io.path.readText
|
||||||
*/
|
*/
|
||||||
class FileSystemPostLoader (
|
class FileSystemPostLoader (
|
||||||
private val postRoot: Path,
|
private val postRoot: Path,
|
||||||
private val fileSystem: FileSystem = object : FileSystem {
|
private val fileSystem: FileSystem = LocalFileSystem(),
|
||||||
override fun readText(path: Path): String = path.readText()
|
|
||||||
},
|
|
||||||
private val yaml: Yaml = Yaml(
|
private val yaml: Yaml = Yaml(
|
||||||
configuration = YamlConfiguration(strictMode = false)
|
configuration = YamlConfiguration(strictMode = false)
|
||||||
)
|
)
|
||||||
) : PostLoader {
|
) : PostLoader {
|
||||||
private val cache = mutableMapOf<String, PostMeta>()
|
private companion object {
|
||||||
|
val COVER_EXTENSIONS = listOf("webp", "png")
|
||||||
|
}
|
||||||
|
|
||||||
override fun getMeta(name: String): PostMeta {
|
override fun readMeta(name: String): PostMeta {
|
||||||
val path = postRoot / name / "meta.yml"
|
val path = postRoot.resolveSafe(name) / "meta.yml"
|
||||||
|
|
||||||
if (!postRoot.isParentOf(path)) {
|
val text = fileSystem.readText(path)
|
||||||
throw IllegalArgumentException("Path traversal ($path not a child of $postRoot)")
|
return yaml.decodeFromString<PostMeta>(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readContent(name: String): String {
|
||||||
|
val path = postRoot.resolveSafe(name) / "content.html"
|
||||||
|
|
||||||
|
return fileSystem.readText(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findCover(name: String): Path {
|
||||||
|
val postPath = postRoot.resolveSafe(name)
|
||||||
|
|
||||||
|
val found = COVER_EXTENSIONS
|
||||||
|
.map { ext -> postPath / "cover.$ext" }
|
||||||
|
.filter { path -> fileSystem.exists(path) }
|
||||||
|
|
||||||
|
if (found.isEmpty()) {
|
||||||
|
throw NoSuchFileException((postPath / "cover.$COVER_EXTENSIONS[0]").toFile(), reason = "No valid cover file found")
|
||||||
|
} else if (found.size > 1) {
|
||||||
|
throw IllegalStateException("Don't know which of ${found.size} cover files to pick: ${found.joinToString(", ")}")
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache.getOrPut(name) {
|
return found.first()
|
||||||
val text = fileSystem.readText(path)
|
|
||||||
yaml.decodeFromString<PostMeta>(text)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package eu.m724.bsk2.builder.post.loader
|
package eu.m724.bsk2.builder.post.loader
|
||||||
|
|
||||||
import eu.m724.bsk2.builder.post.data.PostMeta
|
import eu.m724.bsk2.builder.post.data.PostMeta
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
interface PostLoader {
|
interface PostLoader {
|
||||||
fun getMeta(name: String): PostMeta
|
fun readMeta(name: String): PostMeta
|
||||||
|
fun readContent(name: String): String
|
||||||
|
fun findCover(name: String): Path
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package eu.m724.bsk2.builder.template.loader
|
package eu.m724.bsk2.builder.template.loader
|
||||||
|
|
||||||
import eu.m724.bsk2.builder.FileSystem
|
import eu.m724.bsk2.builder.fs.FileSystem
|
||||||
import eu.m724.bsk2.builder.isParentOf
|
import eu.m724.bsk2.builder.fs.LocalFileSystem
|
||||||
|
import eu.m724.bsk2.builder.fs.resolveSafe
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.div
|
import kotlin.io.path.div
|
||||||
import kotlin.io.path.readText
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A template loader that loads from a file system
|
* A template loader that loads from a file system
|
||||||
|
|
@ -14,22 +14,13 @@ import kotlin.io.path.readText
|
||||||
*/
|
*/
|
||||||
class FileSystemTemplateLoader (
|
class FileSystemTemplateLoader (
|
||||||
templateRoot: Path,
|
templateRoot: Path,
|
||||||
private val fileSystem: FileSystem = object : FileSystem {
|
private val fileSystem: FileSystem = LocalFileSystem()
|
||||||
override fun readText(path: Path): String = path.readText()
|
|
||||||
}
|
|
||||||
) : TemplateLoader {
|
) : TemplateLoader {
|
||||||
private val cache = mutableMapOf<String, String>()
|
|
||||||
private val htmlRoot = templateRoot / "html"
|
private val htmlRoot = templateRoot / "html"
|
||||||
|
|
||||||
override fun getHtml(name: String): String {
|
override fun readHtml(name: String): String {
|
||||||
val path = htmlRoot / "$name.html"
|
val path = htmlRoot.resolveSafe("$name.html")
|
||||||
|
|
||||||
if (!htmlRoot.isParentOf(path)) {
|
return fileSystem.readText(path)
|
||||||
throw IllegalArgumentException("Path traversal ($path not a child of $htmlRoot)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return cache.getOrPut(name) {
|
|
||||||
fileSystem.readText(path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
package eu.m724.bsk2.builder.template.loader
|
package eu.m724.bsk2.builder.template.loader
|
||||||
|
|
||||||
interface TemplateLoader {
|
interface TemplateLoader {
|
||||||
fun getHtml(name: String): String
|
fun readHtml(name: String): String
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package eu.m724.bsk2.builder.post.loader
|
||||||
|
|
||||||
|
import eu.m724.bsk2.builder.fs.FileSystem
|
||||||
|
import io.mockk.*
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.io.path.div
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PostLoaderContentTest {
|
||||||
|
private companion object {
|
||||||
|
val ROOT_PATH: Path = Paths.get("workspace", "posts")
|
||||||
|
|
||||||
|
fun String.toContentHtmlPath() = ROOT_PATH / this / "content.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var fileSystem: FileSystem
|
||||||
|
private lateinit var loader: PostLoader
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setupTest() {
|
||||||
|
fileSystem = mockk<FileSystem>()
|
||||||
|
|
||||||
|
loader = FileSystemPostLoader(
|
||||||
|
postRoot = ROOT_PATH,
|
||||||
|
fileSystem = fileSystem
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun cleanupTest() {
|
||||||
|
checkUnnecessaryStub(fileSystem)
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readContent returns post content on success`() {
|
||||||
|
everyContentHtmlRead("first") returns "Hello, world!"
|
||||||
|
|
||||||
|
assertEquals("Hello, world!", loader.readContent("first"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readContent should throw NoSuchFileException`() {
|
||||||
|
everyContentHtmlRead("non existent") throws NoSuchFileException("non existent".toContentHtmlPath().toFile())
|
||||||
|
|
||||||
|
assertThrows(NoSuchFileException::class.java) { loader.readContent("non existent") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readMeta should throw IllegalArgumentException for path traversal attempts`() {
|
||||||
|
assertThrows(IllegalArgumentException::class.java) { loader.readContent("../traversed") }
|
||||||
|
|
||||||
|
// while not required, here for explicitness
|
||||||
|
verify(exactly = 0) { fileSystem.readText(any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun everyContentHtmlRead(name: String) =
|
||||||
|
every { fileSystem.readText(name.toContentHtmlPath()) }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package eu.m724.bsk2.builder.post.loader
|
||||||
|
|
||||||
|
import eu.m724.bsk2.builder.fs.FileSystem
|
||||||
|
import io.mockk.*
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.io.path.div
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PostLoaderCoverTest{
|
||||||
|
private companion object {
|
||||||
|
val ROOT_PATH: Path = Paths.get("workspace", "posts")
|
||||||
|
|
||||||
|
fun String.toCoverPath(extension: String = "webp") = ROOT_PATH / this / "cover.$extension"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var fileSystem: FileSystem
|
||||||
|
private lateinit var loader: PostLoader
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setupTest() {
|
||||||
|
fileSystem = mockk<FileSystem>()
|
||||||
|
|
||||||
|
loader = FileSystemPostLoader(
|
||||||
|
postRoot = ROOT_PATH,
|
||||||
|
fileSystem = fileSystem
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun cleanupTest() {
|
||||||
|
checkUnnecessaryStub(fileSystem)
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = ["webp", "png"])
|
||||||
|
fun `findCover should find the path to cover`(extension: String) {
|
||||||
|
every { fileSystem.exists(any()) } returns false
|
||||||
|
every { fileSystem.exists("first".toCoverPath(extension)) } returns true
|
||||||
|
|
||||||
|
assertEquals("first".toCoverPath(extension), loader.findCover("first"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `findCover should throw IllegalStateException when there are multiple matches`() {
|
||||||
|
//every { fileSystem.exists(any()) } returns false
|
||||||
|
every { fileSystem.exists("first".toCoverPath("webp")) } returns true
|
||||||
|
every { fileSystem.exists("first".toCoverPath("png")) } returns true
|
||||||
|
|
||||||
|
assertThrows(IllegalStateException::class.java) { loader.findCover("first") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `findCover should throw NoSuchFileException`() {
|
||||||
|
every { fileSystem.exists(any()) } returns false
|
||||||
|
|
||||||
|
assertThrows(NoSuchFileException::class.java) { loader.findCover("first") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `findCover should throw IllegalArgumentException for path traversal attempts`() {
|
||||||
|
assertThrows(IllegalArgumentException::class.java) { loader.findCover("../traversed") }
|
||||||
|
|
||||||
|
// while not required, here for explicitness
|
||||||
|
verify(exactly = 0) { fileSystem.exists(any()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package eu.m724.bsk2.builder.post.loader
|
package eu.m724.bsk2.builder.post.loader
|
||||||
|
|
||||||
import com.charleskorn.kaml.YamlException
|
import com.charleskorn.kaml.YamlException
|
||||||
import eu.m724.bsk2.builder.FileSystem
|
import eu.m724.bsk2.builder.fs.FileSystem
|
||||||
import eu.m724.bsk2.builder.post.data.PostMeta
|
import eu.m724.bsk2.builder.post.data.PostMeta
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
|
@ -13,7 +13,7 @@ import kotlin.io.path.div
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class PostLoaderTest {
|
class PostLoaderMetaTest {
|
||||||
private companion object {
|
private companion object {
|
||||||
val ROOT_PATH: Path = Paths.get("workspace", "posts")
|
val ROOT_PATH: Path = Paths.get("workspace", "posts")
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ class PostLoaderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getPost should cache the result on success`() {
|
fun `readMeta should return the post meta on success`() {
|
||||||
everyMetaYmlRead("first") returns """
|
everyMetaYmlRead("first") returns """
|
||||||
title: Hello, world!
|
title: Hello, world!
|
||||||
summary: My first post
|
summary: My first post
|
||||||
|
|
@ -66,39 +66,32 @@ class PostLoaderTest {
|
||||||
summary = "My second post"
|
summary = "My second post"
|
||||||
)
|
)
|
||||||
|
|
||||||
repeat(3) { assertEquals(expectedFirst, loader.getMeta("first")) }
|
assertEquals(expectedFirst, loader.readMeta("first"))
|
||||||
repeat(3) { assertEquals(expectedSecond, loader.getMeta("second")) }
|
assertEquals(expectedSecond, loader.readMeta("second"))
|
||||||
|
|
||||||
// ensure that cached
|
|
||||||
verify(exactly = 1) { fileSystem.readText("first".toMetaYmlPath()) }
|
verify(exactly = 1) { fileSystem.readText("first".toMetaYmlPath()) }
|
||||||
verify(exactly = 1) { fileSystem.readText("second".toMetaYmlPath()) }
|
verify(exactly = 1) { fileSystem.readText("second".toMetaYmlPath()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getPost should throw YamlException on invalid meta YML file and not cache the failure`() {
|
fun `readMeta should throw YamlException on invalid meta YML file`() {
|
||||||
everyMetaYmlRead("invalid") returns "Totally invalid"
|
everyMetaYmlRead("invalid") returns "Totally invalid"
|
||||||
|
|
||||||
repeat(3) { assertThrows(YamlException::class.java) { loader.getMeta("invalid") } }
|
assertThrows(YamlException::class.java) { loader.readMeta("invalid") }
|
||||||
|
|
||||||
// ensure not cached
|
|
||||||
verify(exactly = 3) { fileSystem.readText("invalid".toMetaYmlPath()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getPost should throw NoSuchFileException and not cache the failure`() {
|
fun `readMeta should throw NoSuchFileException`() {
|
||||||
everyMetaYmlRead("non existent") throws NoSuchFileException("non existent".toMetaYmlPath().toFile())
|
everyMetaYmlRead("non existent") throws NoSuchFileException("non existent".toMetaYmlPath().toFile())
|
||||||
|
|
||||||
repeat(3) { assertThrows(NoSuchFileException::class.java) { loader.getMeta("non existent") } }
|
assertThrows(NoSuchFileException::class.java) { loader.readMeta("non existent") }
|
||||||
|
|
||||||
// ensure not cached
|
|
||||||
verify(exactly = 3) { fileSystem.readText("non existent".toMetaYmlPath()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getPost should throw IllegalArgumentException for path traversal attempts`() {
|
fun `readMeta should throw IllegalArgumentException for path traversal attempts`() {
|
||||||
assertThrows(IllegalArgumentException::class.java) { loader.getMeta("../traversed") }
|
assertThrows(IllegalArgumentException::class.java) { loader.readMeta("../traversed") }
|
||||||
|
|
||||||
// while not necessary, here for explicitness
|
// while not required, here for explicitness
|
||||||
verify(exactly = 0) { fileSystem.readText(any()) }
|
verify(exactly = 0) { fileSystem.readText(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.m724.bsk2.builder.template.loader
|
package eu.m724.bsk2.builder.template.loader
|
||||||
|
|
||||||
import eu.m724.bsk2.builder.FileSystem
|
import eu.m724.bsk2.builder.fs.FileSystem
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
|
@ -14,8 +14,6 @@ import kotlin.test.assertEquals
|
||||||
class TemplateLoaderTest {
|
class TemplateLoaderTest {
|
||||||
private companion object {
|
private companion object {
|
||||||
val ROOT_PATH: Path = Paths.get("workspace", "template")
|
val ROOT_PATH: Path = Paths.get("workspace", "template")
|
||||||
const val POST_TEMPLATE_TEXT = "This is the post template"
|
|
||||||
const val INDEX_TEMPLATE_TEXT = "This is the index template"
|
|
||||||
|
|
||||||
fun String.toHtmlTemplatePath() = (ROOT_PATH / "html" / "$this.html")
|
fun String.toHtmlTemplatePath() = (ROOT_PATH / "html" / "$this.html")
|
||||||
}
|
}
|
||||||
|
|
@ -40,43 +38,25 @@ class TemplateLoaderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getHtml should return file content on success`() {
|
fun `readHtml should return file content on success`() {
|
||||||
everyHtmlTemplateRead("post") returns POST_TEMPLATE_TEXT
|
everyHtmlTemplateRead("post") returns "This is the post template"
|
||||||
everyHtmlTemplateRead("index") returns INDEX_TEMPLATE_TEXT
|
everyHtmlTemplateRead("index") returns "This is the index template"
|
||||||
|
|
||||||
assertEquals(POST_TEMPLATE_TEXT, loader.getHtml("post"))
|
assertEquals("This is the post template", loader.readHtml("post"))
|
||||||
assertEquals(INDEX_TEMPLATE_TEXT, loader.getHtml("index"))
|
assertEquals("This is the index template", loader.readHtml("index"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getHtml should cache the result on success`() {
|
fun `readHtml should throw NoSuchFileException`() {
|
||||||
everyHtmlTemplateRead("post") returns POST_TEMPLATE_TEXT
|
|
||||||
everyHtmlTemplateRead("index") returns INDEX_TEMPLATE_TEXT
|
|
||||||
|
|
||||||
repeat(3) { assertEquals(POST_TEMPLATE_TEXT, loader.getHtml("post")) }
|
|
||||||
repeat(3) { assertEquals(INDEX_TEMPLATE_TEXT, loader.getHtml("index")) }
|
|
||||||
|
|
||||||
// ensure the reads are cached
|
|
||||||
verify(exactly = 1) { fileSystem.readText("post".toHtmlTemplatePath()) }
|
|
||||||
verify(exactly = 1) { fileSystem.readText("index".toHtmlTemplatePath()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getHtml should throw NoSuchFileException and not cache the failure`() {
|
|
||||||
everyHtmlTemplateRead("non existent") throws NoSuchFileException("non existent".toHtmlTemplatePath().toFile())
|
everyHtmlTemplateRead("non existent") throws NoSuchFileException("non existent".toHtmlTemplatePath().toFile())
|
||||||
|
|
||||||
assertThrows(NoSuchFileException::class.java) { loader.getHtml("non existent") }
|
assertThrows(NoSuchFileException::class.java) { loader.readHtml("non existent") }
|
||||||
|
|
||||||
// ensure that the result is not cached
|
|
||||||
assertThrows(NoSuchFileException::class.java) { loader.getHtml("non existent") }
|
|
||||||
|
|
||||||
verify(exactly = 2) { fileSystem.readText("non existent".toHtmlTemplatePath()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getHtml should throw IllegalArgumentException for path traversal attempts`() {
|
fun `readHtml should throw IllegalArgumentException for path traversal attempts`() {
|
||||||
assertThrows(IllegalArgumentException::class.java) {
|
assertThrows(IllegalArgumentException::class.java) {
|
||||||
loader.getHtml("../traversed")
|
loader.readHtml("../traversed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// while not necessary, here for explicitness
|
// while not necessary, here for explicitness
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue