summaryrefslogtreecommitdiff
path: root/platform-paper
diff options
context:
space:
mode:
Diffstat (limited to 'platform-paper')
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelStorageTest.kt121
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/converter/ConversionCacheTest.kt135
2 files changed, 256 insertions, 0 deletions
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelStorageTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelStorageTest.kt
new file mode 100644
index 0000000..2211eef
--- /dev/null
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelStorageTest.kt
@@ -0,0 +1,121 @@
+package dev.m1sk9.lunaticChat.paper.chat.channel
+
+import dev.m1sk9.lunaticChat.engine.chat.channel.Channel
+import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelData
+import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelMember
+import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole
+import dev.m1sk9.lunaticChat.engine.exception.ChannelStorageLoadException
+import dev.m1sk9.lunaticChat.paper.DebouncedSaver
+import dev.m1sk9.lunaticChat.paper.TestUtils
+import io.mockk.mockk
+import io.mockk.slot
+import io.mockk.verify
+import java.nio.file.Files
+import java.nio.file.Path
+import java.util.UUID
+import kotlin.io.path.listDirectoryEntries
+import kotlin.io.path.writeText
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+
+class ChannelStorageTest {
+ private val owner = UUID.fromString("00000001-0000-0000-0000-000000000000")
+
+ private fun sampleData() =
+ ChannelData(
+ version = 1,
+ channels = mapOf("general" to Channel(id = "general", name = "General", ownerId = owner, createdAt = 42)),
+ members =
+ mapOf(
+ "general" to
+ listOf(
+ ChannelMember(
+ channelId = "general",
+ playerId = owner,
+ role = ChannelRole.OWNER,
+ joinedAt = 42,
+ ),
+ ),
+ ),
+ activeChannels = mapOf(owner.toString() to "general"),
+ )
+
+ private fun withStorage(block: (ChannelStorage, Path) -> Unit) {
+ val directory = Files.createTempDirectory("channel-storage-test")
+ try {
+ val channelsFile = directory.resolve("channels.json")
+ block(ChannelStorage(channelsFile, mockk(relaxed = true), TestUtils.TestLogger()), channelsFile)
+ } finally {
+ directory.toFile().deleteRecursively()
+ }
+ }
+
+ @Test
+ fun `saved data is loaded back unchanged`() =
+ withStorage { storage, _ ->
+ val data = sampleData()
+
+ storage.saveToDisk(data)
+
+ assertEquals(data, storage.loadFromDisk())
+ }
+
+ @Test
+ fun `saving leaves no temporary file beside the channel file`() =
+ withStorage { storage, channelsFile ->
+ storage.saveToDisk(sampleData())
+
+ assertEquals(listOf(channelsFile), channelsFile.parent.listDirectoryEntries())
+ }
+
+ @Test
+ fun `loading a missing file yields empty data rather than failing`() =
+ withStorage { storage, _ ->
+ assertEquals(ChannelData(), storage.loadFromDisk())
+ }
+
+ @Test
+ fun `loading an unparseable file fails loudly`() =
+ withStorage { storage, channelsFile ->
+ channelsFile.writeText("{ this is not json")
+
+ assertFailsWith<ChannelStorageLoadException> { storage.loadFromDisk() }
+ }
+
+ @Test
+ fun `unknown fields in the file are ignored`() =
+ withStorage { storage, channelsFile ->
+ channelsFile.writeText("""{"version":1,"channels":{},"members":{},"activeChannels":{},"future":true}""")
+
+ assertEquals(ChannelData(), storage.loadFromDisk())
+ }
+
+ @Test
+ fun `a queued save reads the data when the write runs, not when it is queued`() {
+ val directory = Files.createTempDirectory("channel-storage-test")
+ try {
+ val channelsFile = directory.resolve("channels.json")
+ val saver = mockk<DebouncedSaver>(relaxed = true)
+ val storage = ChannelStorage(channelsFile, saver, TestUtils.TestLogger())
+ val queued = slot<() -> Unit>()
+ var supplied = false
+
+ storage.queueAsyncSave {
+ supplied = true
+ sampleData()
+ }
+
+ verify { saver.request(capture(queued)) }
+ assertTrue(!supplied, "the snapshot must not be taken while queueing")
+
+ queued.captured.invoke()
+
+ assertTrue(supplied)
+ assertEquals(sampleData(), storage.loadFromDisk())
+ } finally {
+ directory.toFile().deleteRecursively()
+ }
+ }
+}
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/converter/ConversionCacheTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/converter/ConversionCacheTest.kt
new file mode 100644
index 0000000..b76c050
--- /dev/null
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/converter/ConversionCacheTest.kt
@@ -0,0 +1,135 @@
+package dev.m1sk9.lunaticChat.paper.converter
+
+import dev.m1sk9.lunaticChat.paper.TestUtils
+import java.nio.file.Files
+import java.nio.file.Path
+import kotlin.io.path.listDirectoryEntries
+import kotlin.io.path.readText
+import kotlin.io.path.writeText
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class ConversionCacheTest {
+ private fun withCacheFile(block: (Path) -> Unit) {
+ val directory = Files.createTempDirectory("conversion-cache-test")
+ try {
+ block(directory.resolve("cache.json"))
+ } finally {
+ directory.toFile().deleteRecursively()
+ }
+ }
+
+ private fun createCache(
+ cacheFile: Path,
+ maxEntries: Int = 500,
+ ) = ConversionCache(cacheFile, maxEntries, TestUtils.TestLogger())
+
+ @Test
+ fun `entries survive a save and reload`() =
+ withCacheFile { cacheFile ->
+ createCache(cacheFile).apply {
+ put("konnichiwa", "こんにちは")
+ put("ohayou", "おはよう")
+ saveToDisk()
+ }
+
+ val reloaded = createCache(cacheFile).apply { loadFromDisk() }
+
+ assertEquals("こんにちは", reloaded.get("konnichiwa"))
+ assertEquals("おはよう", reloaded.get("ohayou"))
+ }
+
+ @Test
+ fun `saving leaves no temporary file beside the cache file`() =
+ withCacheFile { cacheFile ->
+ createCache(cacheFile).apply {
+ put("konnichiwa", "こんにちは")
+ saveToDisk()
+ }
+
+ assertEquals(listOf(cacheFile), cacheFile.parent.listDirectoryEntries())
+ }
+
+ @Test
+ fun `a missing cache file is created empty`() =
+ withCacheFile { cacheFile ->
+ val cache = createCache(cacheFile)
+
+ cache.loadFromDisk()
+
+ assertTrue(Files.exists(cacheFile))
+ assertNull(cache.get("konnichiwa"))
+ }
+
+ @Test
+ fun `a cache written by an incompatible version is discarded`() =
+ withCacheFile { cacheFile ->
+ cacheFile.writeText("""{"version":"0","entries":{"konnichiwa":"こんにちは"}}""")
+ val cache = createCache(cacheFile)
+
+ cache.loadFromDisk()
+
+ assertNull(cache.get("konnichiwa"))
+ }
+
+ @Test
+ fun `an unparseable cache file is discarded rather than failing the load`() =
+ withCacheFile { cacheFile ->
+ cacheFile.writeText("{ this is not json")
+ val cache = createCache(cacheFile)
+
+ cache.loadFromDisk()
+
+ assertNull(cache.get("konnichiwa"))
+ }
+
+ @Test
+ fun `an unchanged cache is not rewritten`() =
+ withCacheFile { cacheFile ->
+ val cache =
+ createCache(cacheFile).apply {
+ put("konnichiwa", "こんにちは")
+ saveToDisk()
+ }
+ // The periodic task fires on a fixed interval whether or not anyone chatted, so a clean
+ // cache must cost nothing.
+ cacheFile.writeText("sentinel")
+
+ cache.saveToDisk()
+
+ assertEquals("sentinel", cacheFile.readText())
+ }
+
+ @Test
+ fun `a change since the last save is written`() =
+ withCacheFile { cacheFile ->
+ val cache =
+ createCache(cacheFile).apply {
+ put("konnichiwa", "こんにちは")
+ saveToDisk()
+ }
+ cacheFile.writeText("sentinel")
+
+ cache.put("ohayou", "おはよう")
+ cache.saveToDisk()
+
+ assertNotEquals("sentinel", cacheFile.readText())
+ assertEquals("おはよう", createCache(cacheFile).apply { loadFromDisk() }.get("ohayou"))
+ }
+
+ @Test
+ fun `the cache stays within its entry limit`() =
+ withCacheFile { cacheFile ->
+ val cache = createCache(cacheFile, maxEntries = 10)
+
+ repeat(20) { cache.put("word$it", "変換$it") }
+ cache.saveToDisk()
+
+ val entries = createCache(cacheFile).apply { loadFromDisk() }
+ assertEquals("変換19", entries.get("word19"))
+ assertTrue((0 until 20).count { entries.get("word$it") != null } <= 10)
+ }
+}