diff options
Diffstat (limited to 'platform-paper')
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) + } +} |
