summaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-02-25 22:38:19 +0900
committerSho Sakuma <me@m1sk9.dev>2026-02-25 22:38:44 +0900
commitf4bf20791f4470d3e9b865a53b610b5f277ebb82 (patch)
tree7d0b3f9d3eff87730413290d4436b43ff6146b7a /engine
parent56ed04185036635dcfd2763391cd64b8be371b1a (diff)
downloadLunaticChat-f4bf20791f4470d3e9b865a53b610b5f277ebb82.tar.gz
LunaticChat-f4bf20791f4470d3e9b865a53b610b5f277ebb82.tar.bz2
LunaticChat-f4bf20791f4470d3e9b865a53b610b5f277ebb82.zip
feat: add Codecov/Jacoco integration and expand test coverage
- Add Jacoco plugin to all subprojects with XML report generation - Replace post-test-results.sh with Codecov upload in CI workflow - Add codecov.yml configuration - Add 140 new tests across engine, platform-paper, and platform-velocity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'engine')
-rw-r--r--engine/build.gradle.kts2
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeTest.kt33
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelTest.kt139
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/command/CommandResultTest.kt54
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/PluginMessageCodecTest.kt233
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/ProtocolVersionTest.kt57
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/UUIDSerializerTest.kt100
7 files changed, 618 insertions, 0 deletions
diff --git a/engine/build.gradle.kts b/engine/build.gradle.kts
index c178870..0f4e419 100644
--- a/engine/build.gradle.kts
+++ b/engine/build.gradle.kts
@@ -12,4 +12,6 @@ dependencies {
// Adventure API (provided by platform implementations)
compileOnly("net.kyori:adventure-api:4.26.1")
+
+ testImplementation("net.kyori:adventure-api:4.26.1")
}
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeTest.kt
new file mode 100644
index 0000000..5656a14
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeTest.kt
@@ -0,0 +1,33 @@
+package dev.m1sk9.lunaticChat.engine.chat
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ChatModeTest {
+ @Test
+ fun `toggle should switch GLOBAL to CHANNEL`() {
+ assertEquals(ChatMode.CHANNEL, ChatMode.GLOBAL.toggle())
+ }
+
+ @Test
+ fun `toggle should switch CHANNEL to GLOBAL`() {
+ assertEquals(ChatMode.GLOBAL, ChatMode.CHANNEL.toggle())
+ }
+
+ @Test
+ fun `DEFAULT should be GLOBAL`() {
+ assertEquals(ChatMode.GLOBAL, ChatMode.DEFAULT)
+ }
+
+ @Test
+ fun `double toggle should return original mode`() {
+ assertEquals(ChatMode.GLOBAL, ChatMode.GLOBAL.toggle().toggle())
+ assertEquals(ChatMode.CHANNEL, ChatMode.CHANNEL.toggle().toggle())
+ }
+
+ @Test
+ fun `enum should have exactly two values`() {
+ assertEquals(2, ChatMode.entries.size)
+ assertEquals(setOf(ChatMode.GLOBAL, ChatMode.CHANNEL), ChatMode.entries.toSet())
+ }
+}
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelTest.kt
new file mode 100644
index 0000000..b24e169
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelTest.kt
@@ -0,0 +1,139 @@
+package dev.m1sk9.lunaticChat.engine.chat.channel
+
+import kotlinx.serialization.json.Json
+import java.util.UUID
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class ChannelTest {
+ private val testOwnerId = UUID.fromString("00000001-0000-0000-0000-000000000000")
+ private val json = Json { ignoreUnknownKeys = true }
+
+ @Test
+ fun `valid channel ID should be accepted`() {
+ val channel = Channel(id = "test-channel", name = "Test", ownerId = testOwnerId)
+ assertEquals("test-channel", channel.id)
+ }
+
+ @Test
+ fun `channel ID with underscores and hyphens should be accepted`() {
+ val channel = Channel(id = "my_test-channel", name = "Test", ownerId = testOwnerId)
+ assertEquals("my_test-channel", channel.id)
+ }
+
+ @Test
+ fun `channel ID with exactly 3 characters should be accepted`() {
+ val channel = Channel(id = "abc", name = "Test", ownerId = testOwnerId)
+ assertEquals("abc", channel.id)
+ }
+
+ @Test
+ fun `channel ID with exactly 30 characters should be accepted`() {
+ val id = "a".repeat(30)
+ val channel = Channel(id = id, name = "Test", ownerId = testOwnerId)
+ assertEquals(id, channel.id)
+ }
+
+ @Test
+ fun `channel ID with less than 3 characters should be rejected`() {
+ assertFailsWith<IllegalArgumentException> {
+ Channel(id = "ab", name = "Test", ownerId = testOwnerId)
+ }
+ }
+
+ @Test
+ fun `channel ID with more than 30 characters should be rejected`() {
+ assertFailsWith<IllegalArgumentException> {
+ Channel(id = "a".repeat(31), name = "Test", ownerId = testOwnerId)
+ }
+ }
+
+ @Test
+ fun `channel ID with spaces should be rejected`() {
+ assertFailsWith<IllegalArgumentException> {
+ Channel(id = "test channel", name = "Test", ownerId = testOwnerId)
+ }
+ }
+
+ @Test
+ fun `channel ID with special characters should be rejected`() {
+ assertFailsWith<IllegalArgumentException> {
+ Channel(id = "test@channel", name = "Test", ownerId = testOwnerId)
+ }
+ }
+
+ @Test
+ fun `empty channel ID should be rejected`() {
+ assertFailsWith<IllegalArgumentException> {
+ Channel(id = "", name = "Test", ownerId = testOwnerId)
+ }
+ }
+
+ @Test
+ fun `blank channel name should be rejected`() {
+ assertFailsWith<IllegalArgumentException> {
+ Channel(id = "valid-id", name = " ", ownerId = testOwnerId)
+ }
+ }
+
+ @Test
+ fun `empty channel name should be rejected`() {
+ assertFailsWith<IllegalArgumentException> {
+ Channel(id = "valid-id", name = "", ownerId = testOwnerId)
+ }
+ }
+
+ @Test
+ fun `default values should be correct`() {
+ val channel = Channel(id = "test-ch", name = "Test Channel", ownerId = testOwnerId)
+ assertNull(channel.description)
+ assertFalse(channel.isPrivate)
+ assertTrue(channel.bannedPlayers.isEmpty())
+ }
+
+ @Test
+ fun `serialization round-trip should preserve all fields`() {
+ val bannedPlayer = UUID.fromString("00000002-0000-0000-0000-000000000000")
+ val original =
+ Channel(
+ id = "test-channel",
+ name = "Test Channel",
+ description = "A test channel",
+ isPrivate = true,
+ ownerId = testOwnerId,
+ createdAt = 1000L,
+ bannedPlayers = setOf(bannedPlayer),
+ )
+
+ val jsonString = json.encodeToString(Channel.serializer(), original)
+ val decoded = json.decodeFromString(Channel.serializer(), jsonString)
+
+ assertEquals(original.id, decoded.id)
+ assertEquals(original.name, decoded.name)
+ assertEquals(original.description, decoded.description)
+ assertEquals(original.isPrivate, decoded.isPrivate)
+ assertEquals(original.ownerId, decoded.ownerId)
+ assertEquals(original.createdAt, decoded.createdAt)
+ assertEquals(original.bannedPlayers, decoded.bannedPlayers)
+ }
+
+ @Test
+ fun `CHANNEL_ID_PATTERN should match valid patterns`() {
+ assertTrue("abc".matches(Channel.CHANNEL_ID_PATTERN))
+ assertTrue("test-123".matches(Channel.CHANNEL_ID_PATTERN))
+ assertTrue("my_channel".matches(Channel.CHANNEL_ID_PATTERN))
+ assertTrue("ABC123".matches(Channel.CHANNEL_ID_PATTERN))
+ }
+
+ @Test
+ fun `CHANNEL_ID_PATTERN should reject invalid patterns`() {
+ assertFalse("ab".matches(Channel.CHANNEL_ID_PATTERN))
+ assertFalse("".matches(Channel.CHANNEL_ID_PATTERN))
+ assertFalse("test channel".matches(Channel.CHANNEL_ID_PATTERN))
+ assertFalse("test@ch".matches(Channel.CHANNEL_ID_PATTERN))
+ }
+}
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/command/CommandResultTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/command/CommandResultTest.kt
new file mode 100644
index 0000000..b80f116
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/command/CommandResultTest.kt
@@ -0,0 +1,54 @@
+package dev.m1sk9.lunaticChat.engine.command
+
+import net.kyori.adventure.text.Component
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+
+class CommandResultTest {
+ @Test
+ fun `Success toBrigadierResult should return 1`() {
+ assertEquals(1, CommandResult.Success.toBrigadierResult())
+ }
+
+ @Test
+ fun `SuccessWithMessage toBrigadierResult should return 1`() {
+ val result = CommandResult.SuccessWithMessage(Component.text("ok"))
+ assertEquals(1, result.toBrigadierResult())
+ }
+
+ @Test
+ fun `Failure toBrigadierResult should return 0`() {
+ val result = CommandResult.Failure(Component.text("error"))
+ assertEquals(0, result.toBrigadierResult())
+ }
+
+ @Test
+ fun `InvalidUsage toBrigadierResult should return 0`() {
+ val result = CommandResult.InvalidUsage("/cmd <arg>")
+ assertEquals(0, result.toBrigadierResult())
+ }
+
+ @Test
+ fun `SuccessWithMessage should preserve message`() {
+ val message = Component.text("Test message")
+ val result = CommandResult.SuccessWithMessage(message)
+ assertIs<CommandResult.SuccessWithMessage>(result)
+ assertEquals(message, result.message)
+ }
+
+ @Test
+ fun `Failure should preserve message`() {
+ val message = Component.text("Error message")
+ val result = CommandResult.Failure(message)
+ assertIs<CommandResult.Failure>(result)
+ assertEquals(message, result.message)
+ }
+
+ @Test
+ fun `InvalidUsage should preserve usage hint`() {
+ val result = CommandResult.InvalidUsage("/lc setting <key> <on|off>")
+ assertIs<CommandResult.InvalidUsage>(result)
+ assertEquals("/lc setting <key> <on|off>", result.usageHint)
+ }
+}
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/PluginMessageCodecTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/PluginMessageCodecTest.kt
new file mode 100644
index 0000000..c792aad
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/PluginMessageCodecTest.kt
@@ -0,0 +1,233 @@
+package dev.m1sk9.lunaticChat.engine.protocol
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
+
+class PluginMessageCodecTest {
+ @Test
+ fun `encode and decode Handshake round-trip`() {
+ val original =
+ PluginMessage.Handshake(
+ pluginVersion = "0.10.0",
+ protocolMajor = 1,
+ protocolMinor = 0,
+ protocolPatch = 0,
+ )
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.Handshake>(decoded)
+ assertEquals(original.pluginVersion, decoded.pluginVersion)
+ assertEquals(original.protocolMajor, decoded.protocolMajor)
+ assertEquals(original.protocolMinor, decoded.protocolMinor)
+ assertEquals(original.protocolPatch, decoded.protocolPatch)
+ }
+
+ @Test
+ fun `encode and decode HandshakeResponse compatible round-trip`() {
+ val original =
+ PluginMessage.HandshakeResponse(
+ compatible = true,
+ velocityVersion = "0.10.0",
+ error = null,
+ )
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.HandshakeResponse>(decoded)
+ assertEquals(original.compatible, decoded.compatible)
+ assertEquals(original.velocityVersion, decoded.velocityVersion)
+ assertEquals(original.error, decoded.error)
+ }
+
+ @Test
+ fun `encode and decode HandshakeResponse incompatible round-trip`() {
+ val original =
+ PluginMessage.HandshakeResponse(
+ compatible = false,
+ velocityVersion = "0.10.0",
+ error = "Version mismatch",
+ )
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.HandshakeResponse>(decoded)
+ assertEquals(false, decoded.compatible)
+ assertEquals("Version mismatch", decoded.error)
+ }
+
+ @Test
+ fun `encode and decode StatusRequest round-trip`() {
+ val original = PluginMessage.StatusRequest
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.StatusRequest>(decoded)
+ }
+
+ @Test
+ fun `encode and decode StatusResponse round-trip`() {
+ val original =
+ PluginMessage.StatusResponse(
+ velocityVersion = "0.10.0",
+ protocolVersion = "1.0.0",
+ online = true,
+ )
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.StatusResponse>(decoded)
+ assertEquals(original.velocityVersion, decoded.velocityVersion)
+ assertEquals(original.protocolVersion, decoded.protocolVersion)
+ assertEquals(original.online, decoded.online)
+ }
+
+ @Test
+ fun `encode and decode GlobalChatMessage round-trip`() {
+ val original =
+ PluginMessage.GlobalChatMessage(
+ messageId = "test-id-123",
+ serverName = "lobby",
+ playerId = "00000001-0000-0000-0000-000000000000",
+ playerName = "TestPlayer",
+ message = "Hello, world!",
+ timestamp = 1000L,
+ )
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.GlobalChatMessage>(decoded)
+ assertEquals(original.messageId, decoded.messageId)
+ assertEquals(original.serverName, decoded.serverName)
+ assertEquals(original.playerId, decoded.playerId)
+ assertEquals(original.playerName, decoded.playerName)
+ assertEquals(original.message, decoded.message)
+ assertEquals(original.timestamp, decoded.timestamp)
+ }
+
+ @Test
+ fun `decode should throw on unknown sub-channel`() {
+ val out = java.io.ByteArrayOutputStream()
+ val dataOut = java.io.DataOutputStream(out)
+ dataOut.writeUTF("unknown_channel")
+ dataOut.writeUTF("{}")
+
+ assertFailsWith<IllegalArgumentException> {
+ PluginMessageCodec.decode(out.toByteArray())
+ }
+ }
+
+ @Test
+ fun `decode should throw on empty data`() {
+ assertFailsWith<Exception> {
+ PluginMessageCodec.decode(byteArrayOf())
+ }
+ }
+
+ @Test
+ fun `encode and decode GlobalChatMessage with special characters`() {
+ val original =
+ PluginMessage.GlobalChatMessage(
+ messageId = "msg-special",
+ serverName = "survival",
+ playerId = "00000002-0000-0000-0000-000000000000",
+ playerName = "Player_With-Dash",
+ message = "Hello! こんにちは 🎉 \"quotes\" & <tags>",
+ timestamp = 2000L,
+ )
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.GlobalChatMessage>(decoded)
+ assertEquals(original.message, decoded.message)
+ }
+
+ @Test
+ fun `SubChannel constants should have correct values`() {
+ assertEquals("handshake", PluginMessageCodec.SubChannel.HANDSHAKE)
+ assertEquals("handshake_response", PluginMessageCodec.SubChannel.HANDSHAKE_RESPONSE)
+ assertEquals("status_request", PluginMessageCodec.SubChannel.STATUS_REQUEST)
+ assertEquals("status_response", PluginMessageCodec.SubChannel.STATUS_RESPONSE)
+ assertEquals("global_chat", PluginMessageCodec.SubChannel.GLOBAL_CHAT)
+ }
+
+ @Test
+ fun `encode should produce non-empty byte array for all message types`() {
+ val messages =
+ listOf(
+ PluginMessage.Handshake("1.0.0", 1, 0, 0),
+ PluginMessage.HandshakeResponse(true, "1.0.0"),
+ PluginMessage.StatusRequest,
+ PluginMessage.StatusResponse("1.0.0", "1.0.0", true),
+ PluginMessage.GlobalChatMessage("id", "srv", "pid", "name", "msg", 0L),
+ )
+
+ messages.forEach { message ->
+ val encoded = PluginMessageCodec.encode(message)
+ assert(encoded.isNotEmpty()) { "Encoded ${message::class.simpleName} should not be empty" }
+ }
+ }
+
+ @Test
+ fun `StatusResponse with online false round-trip`() {
+ val original =
+ PluginMessage.StatusResponse(
+ velocityVersion = "0.10.0",
+ protocolVersion = "1.0.0",
+ online = false,
+ )
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.StatusResponse>(decoded)
+ assertEquals(false, decoded.online)
+ }
+
+ @Test
+ fun `Handshake with various protocol versions round-trip`() {
+ val original =
+ PluginMessage.Handshake(
+ pluginVersion = "2.5.3",
+ protocolMajor = 99,
+ protocolMinor = 42,
+ protocolPatch = 7,
+ )
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.Handshake>(decoded)
+ assertEquals(99, decoded.protocolMajor)
+ assertEquals(42, decoded.protocolMinor)
+ assertEquals(7, decoded.protocolPatch)
+ }
+
+ @Test
+ fun `GlobalChatMessage with empty message round-trip`() {
+ val original =
+ PluginMessage.GlobalChatMessage(
+ messageId = "msg-empty",
+ serverName = "lobby",
+ playerId = "00000003-0000-0000-0000-000000000000",
+ playerName = "Player",
+ message = "",
+ timestamp = 3000L,
+ )
+
+ val encoded = PluginMessageCodec.encode(original)
+ val decoded = PluginMessageCodec.decode(encoded)
+
+ assertIs<PluginMessage.GlobalChatMessage>(decoded)
+ assertEquals("", decoded.message)
+ }
+}
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/ProtocolVersionTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/ProtocolVersionTest.kt
new file mode 100644
index 0000000..7f05039
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/ProtocolVersionTest.kt
@@ -0,0 +1,57 @@
+package dev.m1sk9.lunaticChat.engine.protocol
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class ProtocolVersionTest {
+ @Test
+ fun `version string should match MAJOR MINOR PATCH format`() {
+ assertEquals("${ProtocolVersion.MAJOR}.${ProtocolVersion.MINOR}.${ProtocolVersion.PATCH}", ProtocolVersion.version)
+ }
+
+ @Test
+ fun `isCompatible with matching major and minor should return true`() {
+ assertTrue(ProtocolVersion.isCompatible(ProtocolVersion.MAJOR, ProtocolVersion.MINOR))
+ }
+
+ @Test
+ fun `isCompatible with different major should return false`() {
+ assertFalse(ProtocolVersion.isCompatible(ProtocolVersion.MAJOR + 1, ProtocolVersion.MINOR))
+ }
+
+ @Test
+ fun `isCompatible with different minor should return false`() {
+ assertFalse(ProtocolVersion.isCompatible(ProtocolVersion.MAJOR, ProtocolVersion.MINOR + 1))
+ }
+
+ @Test
+ fun `isCompatible string with matching version should return true`() {
+ assertTrue(ProtocolVersion.isCompatible("${ProtocolVersion.MAJOR}.${ProtocolVersion.MINOR}.0"))
+ }
+
+ @Test
+ fun `isCompatible string with different patch should return true`() {
+ assertTrue(ProtocolVersion.isCompatible("${ProtocolVersion.MAJOR}.${ProtocolVersion.MINOR}.99"))
+ }
+
+ @Test
+ fun `isCompatible string with different major should return false`() {
+ assertFalse(ProtocolVersion.isCompatible("${ProtocolVersion.MAJOR + 1}.${ProtocolVersion.MINOR}.0"))
+ }
+
+ @Test
+ fun `isCompatible string with malformed version should return false`() {
+ assertFalse(ProtocolVersion.isCompatible("invalid"))
+ assertFalse(ProtocolVersion.isCompatible("1.0"))
+ assertFalse(ProtocolVersion.isCompatible(""))
+ assertFalse(ProtocolVersion.isCompatible("a.b.c"))
+ assertFalse(ProtocolVersion.isCompatible("1.0.0.0"))
+ }
+
+ @Test
+ fun `isCompatible string with two parts should return false`() {
+ assertFalse(ProtocolVersion.isCompatible("1.0"))
+ }
+}
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/UUIDSerializerTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/UUIDSerializerTest.kt
new file mode 100644
index 0000000..599a372
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/UUIDSerializerTest.kt
@@ -0,0 +1,100 @@
+package dev.m1sk9.lunaticChat.engine.settings
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
+import java.util.UUID
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class UUIDSerializerTest {
+ private val json = Json
+
+ @Serializable
+ private data class UUIDHolder(
+ @Serializable(with = UUIDSerializer::class)
+ val uuid: UUID,
+ )
+
+ @Serializable
+ private data class UUIDAsStringHolder(
+ @Serializable(with = UUIDASStringSerializer::class)
+ val uuid: UUID,
+ )
+
+ @Test
+ fun `UUIDSerializer should serialize UUID to string`() {
+ val uuid = UUID.fromString("12345678-1234-1234-1234-123456789abc")
+ val holder = UUIDHolder(uuid)
+
+ val jsonString = json.encodeToString(UUIDHolder.serializer(), holder)
+
+ assert(jsonString.contains("12345678-1234-1234-1234-123456789abc"))
+ }
+
+ @Test
+ fun `UUIDSerializer should deserialize string to UUID`() {
+ val jsonString = """{"uuid":"12345678-1234-1234-1234-123456789abc"}"""
+ val holder = json.decodeFromString(UUIDHolder.serializer(), jsonString)
+
+ assertEquals(UUID.fromString("12345678-1234-1234-1234-123456789abc"), holder.uuid)
+ }
+
+ @Test
+ fun `UUIDSerializer round-trip should preserve UUID`() {
+ val originalUuid = UUID.randomUUID()
+ val holder = UUIDHolder(originalUuid)
+
+ val jsonString = json.encodeToString(UUIDHolder.serializer(), holder)
+ val decoded = json.decodeFromString(UUIDHolder.serializer(), jsonString)
+
+ assertEquals(originalUuid, decoded.uuid)
+ }
+
+ @Test
+ fun `UUIDASStringSerializer should serialize UUID to string`() {
+ val uuid = UUID.fromString("abcdef01-2345-6789-abcd-ef0123456789")
+ val holder = UUIDAsStringHolder(uuid)
+
+ val jsonString = json.encodeToString(UUIDAsStringHolder.serializer(), holder)
+
+ assert(jsonString.contains("abcdef01-2345-6789-abcd-ef0123456789"))
+ }
+
+ @Test
+ fun `UUIDASStringSerializer should deserialize string to UUID`() {
+ val jsonString = """{"uuid":"abcdef01-2345-6789-abcd-ef0123456789"}"""
+ val holder = json.decodeFromString(UUIDAsStringHolder.serializer(), jsonString)
+
+ assertEquals(UUID.fromString("abcdef01-2345-6789-abcd-ef0123456789"), holder.uuid)
+ }
+
+ @Test
+ fun `UUIDASStringSerializer round-trip should preserve UUID`() {
+ val originalUuid = UUID.randomUUID()
+ val holder = UUIDAsStringHolder(originalUuid)
+
+ val jsonString = json.encodeToString(UUIDAsStringHolder.serializer(), holder)
+ val decoded = json.decodeFromString(UUIDAsStringHolder.serializer(), jsonString)
+
+ assertEquals(originalUuid, decoded.uuid)
+ }
+
+ @Test
+ fun `UUIDSerializer should fail on invalid UUID string`() {
+ val jsonString = """{"uuid":"not-a-valid-uuid"}"""
+
+ assertFailsWith<Exception> {
+ json.decodeFromString(UUIDHolder.serializer(), jsonString)
+ }
+ }
+
+ @Test
+ fun `UUIDASStringSerializer should fail on invalid UUID string`() {
+ val jsonString = """{"uuid":"not-a-valid-uuid"}"""
+
+ assertFailsWith<Exception> {
+ json.decodeFromString(UUIDAsStringHolder.serializer(), jsonString)
+ }
+ }
+}