summaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-02-26 00:17:21 +0900
committerSho Sakuma <me@m1sk9.dev>2026-02-26 00:17:21 +0900
commit70c785eee4c61601ba7fffa17cc8b992b4793491 (patch)
tree2bb41b90c19cfd3cfbe638f9f8fbd420ab96aa86 /engine
parent365d9ede9d0abaae2bad03231b6bc5243013fc92 (diff)
downloadLunaticChat-70c785eee4c61601ba7fffa17cc8b992b4793491.tar.gz
LunaticChat-70c785eee4c61601ba7fffa17cc8b992b4793491.tar.bz2
LunaticChat-70c785eee4c61601ba7fffa17cc8b992b4793491.zip
test: expand test coverage across all modules
- Add 23 new test files covering exceptions, data classes, permissions, setting handlers, commands, and cross-server relay - Change command execute methods from private to internal visibility to enable unit testing (16 command files) - Engine coverage: 65.5% → 92.4% - Platform-paper coverage: 16.8% → 40.0% - Platform-velocity coverage: 62.4% → 76.8% Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'engine')
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelDataClassesTest.kt200
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelExceptionTest.kt279
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/permission/LunaticChatPermissionNodeTest.kt82
-rw-r--r--engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/SettingsDataClassesTest.kt167
4 files changed, 728 insertions, 0 deletions
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelDataClassesTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelDataClassesTest.kt
new file mode 100644
index 0000000..4684432
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelDataClassesTest.kt
@@ -0,0 +1,200 @@
+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.assertNotEquals
+import kotlin.test.assertTrue
+
+class ChannelDataClassesTest {
+ private val json = Json { ignoreUnknownKeys = true }
+ private val testOwnerId = UUID.fromString("00000001-0000-0000-0000-000000000000")
+ private val testPlayerId = UUID.fromString("00000002-0000-0000-0000-000000000000")
+
+ // --- ChannelRole ---
+
+ @Test
+ fun `ChannelRole should have 3 entries`() {
+ assertEquals(3, ChannelRole.entries.size)
+ }
+
+ @Test
+ fun `ChannelRole entries should be OWNER, MODERATOR, MEMBER`() {
+ val expected = setOf("OWNER", "MODERATOR", "MEMBER")
+ val actual = ChannelRole.entries.map { it.name }.toSet()
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `ChannelRole serialization round-trip should preserve value`() {
+ ChannelRole.entries.forEach { role ->
+ val serialized = json.encodeToString(ChannelRole.serializer(), role)
+ val deserialized = json.decodeFromString(ChannelRole.serializer(), serialized)
+ assertEquals(role, deserialized)
+ }
+ }
+
+ // --- ChannelMember ---
+
+ @Test
+ fun `ChannelMember should have default joinedAt`() {
+ val before = System.currentTimeMillis()
+ val member = ChannelMember(channelId = "ch-1", playerId = testPlayerId, role = ChannelRole.MEMBER)
+ val after = System.currentTimeMillis()
+
+ assertTrue(member.joinedAt in before..after)
+ }
+
+ @Test
+ fun `ChannelMember serialization round-trip should preserve all fields`() {
+ val original =
+ ChannelMember(
+ channelId = "ch-1",
+ playerId = testPlayerId,
+ role = ChannelRole.MODERATOR,
+ joinedAt = 12345L,
+ )
+
+ val serialized = json.encodeToString(ChannelMember.serializer(), original)
+ val deserialized = json.decodeFromString(ChannelMember.serializer(), serialized)
+
+ assertEquals(original.channelId, deserialized.channelId)
+ assertEquals(original.playerId, deserialized.playerId)
+ assertEquals(original.role, deserialized.role)
+ assertEquals(original.joinedAt, deserialized.joinedAt)
+ }
+
+ @Test
+ fun `ChannelMember copy should allow role change`() {
+ val original = ChannelMember(channelId = "ch-1", playerId = testPlayerId, role = ChannelRole.MEMBER)
+ val promoted = original.copy(role = ChannelRole.MODERATOR)
+
+ assertEquals(ChannelRole.MODERATOR, promoted.role)
+ assertEquals(original.channelId, promoted.channelId)
+ assertEquals(original.playerId, promoted.playerId)
+ assertEquals(original.joinedAt, promoted.joinedAt)
+ }
+
+ // --- ChannelData ---
+
+ @Test
+ fun `ChannelData default values should be correct`() {
+ val data = ChannelData()
+ assertEquals(1, data.version)
+ assertTrue(data.channels.isEmpty())
+ assertTrue(data.members.isEmpty())
+ assertTrue(data.activeChannels.isEmpty())
+ }
+
+ @Test
+ fun `ChannelData serialization round-trip should preserve all fields`() {
+ val channel = Channel(id = "ch-1", name = "Test Channel", ownerId = testOwnerId, createdAt = 1000L)
+ val member = ChannelMember(channelId = "ch-1", playerId = testPlayerId, role = ChannelRole.MEMBER, joinedAt = 2000L)
+ val original =
+ ChannelData(
+ version = 1,
+ channels = mapOf("ch-1" to channel),
+ members = mapOf("ch-1" to listOf(member)),
+ activeChannels = mapOf("player1" to "ch-1"),
+ )
+
+ val serialized = json.encodeToString(ChannelData.serializer(), original)
+ val deserialized = json.decodeFromString(ChannelData.serializer(), serialized)
+
+ assertEquals(original.version, deserialized.version)
+ assertEquals(original.channels.size, deserialized.channels.size)
+ assertEquals(original.members.size, deserialized.members.size)
+ assertEquals(original.activeChannels, deserialized.activeChannels)
+ }
+
+ // --- ChannelContext ---
+
+ @Test
+ fun `ChannelContext should store all fields`() {
+ val channel = Channel(id = "ch-1", name = "Test", ownerId = testOwnerId)
+ val member = ChannelMember(channelId = "ch-1", playerId = testPlayerId, role = ChannelRole.MEMBER)
+ val context = ChannelContext(channelId = "ch-1", channel = channel, members = listOf(member))
+
+ assertEquals("ch-1", context.channelId)
+ assertEquals(channel, context.channel)
+ assertEquals(1, context.members.size)
+ assertEquals(testPlayerId, context.members[0].playerId)
+ }
+
+ @Test
+ fun `ChannelContext copy should create independent instance`() {
+ val channel = Channel(id = "ch-1", name = "Test", ownerId = testOwnerId)
+ val original = ChannelContext(channelId = "ch-1", channel = channel, members = emptyList())
+ val copied = original.copy(channelId = "ch-2")
+
+ assertEquals("ch-2", copied.channelId)
+ assertNotEquals(original.channelId, copied.channelId)
+ }
+
+ // --- ChannelMessageLogEntry ---
+
+ @Test
+ fun `ChannelMessageLogEntry create should convert UUID to string`() {
+ val entry =
+ ChannelMessageLogEntry.create(
+ playerId = testPlayerId,
+ playerName = "TestPlayer",
+ channelId = "ch-1",
+ message = "Hello world",
+ )
+
+ assertEquals(testPlayerId.toString(), entry.playerId)
+ assertEquals("TestPlayer", entry.playerName)
+ assertEquals("ch-1", entry.channelId)
+ assertEquals("Hello world", entry.message)
+ }
+
+ @Test
+ fun `ChannelMessageLogEntry create should set non-empty timestamp`() {
+ val entry =
+ ChannelMessageLogEntry.create(
+ playerId = testPlayerId,
+ playerName = "TestPlayer",
+ channelId = "ch-1",
+ message = "Hello",
+ )
+
+ assertTrue(entry.timestamp.isNotEmpty())
+ }
+
+ @Test
+ fun `ChannelMessageLogEntry serialization round-trip should preserve all fields`() {
+ val original =
+ ChannelMessageLogEntry(
+ timestamp = "2024-01-01T00:00:00Z",
+ playerId = testPlayerId.toString(),
+ playerName = "TestPlayer",
+ channelId = "ch-1",
+ message = "Test message",
+ )
+
+ val serialized = json.encodeToString(ChannelMessageLogEntry.serializer(), original)
+ val deserialized = json.decodeFromString(ChannelMessageLogEntry.serializer(), serialized)
+
+ assertEquals(original.timestamp, deserialized.timestamp)
+ assertEquals(original.playerId, deserialized.playerId)
+ assertEquals(original.playerName, deserialized.playerName)
+ assertEquals(original.channelId, deserialized.channelId)
+ assertEquals(original.message, deserialized.message)
+ }
+
+ @Test
+ fun `ChannelMessageLogEntry create timestamp should be ISO-8601 format`() {
+ val entry =
+ ChannelMessageLogEntry.create(
+ playerId = testPlayerId,
+ playerName = "TestPlayer",
+ channelId = "ch-1",
+ message = "Hello",
+ )
+
+ // ISO-8601 timestamps contain 'T' separator
+ assertTrue(entry.timestamp.contains("T"), "Timestamp should be ISO-8601 format: ${entry.timestamp}")
+ }
+}
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelExceptionTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelExceptionTest.kt
new file mode 100644
index 0000000..955c96b
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/exception/ChannelExceptionTest.kt
@@ -0,0 +1,279 @@
+package dev.m1sk9.lunaticChat.engine.exception
+
+import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode
+import java.util.UUID
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class ChannelExceptionTest {
+ private val testPlayerId = UUID.fromString("00000001-0000-0000-0000-000000000000")
+ private val testChannelId = "test-channel"
+
+ // --- ChannelAlreadyActiveException ---
+
+ @Test
+ fun `ChannelAlreadyActiveException should contain playerId and channelId in message`() {
+ val ex = ChannelAlreadyActiveException(testPlayerId, testChannelId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertTrue(ex.message!!.contains(testChannelId))
+ assertEquals(testPlayerId, ex.playerId)
+ assertEquals(testChannelId, ex.channelId)
+ }
+
+ // --- ChannelCannotInviteSelfException ---
+
+ @Test
+ fun `ChannelCannotInviteSelfException should contain playerId in message`() {
+ val ex = ChannelCannotInviteSelfException(testPlayerId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ }
+
+ // --- ChannelLimitExceededException ---
+
+ @Test
+ fun `ChannelLimitExceededException should contain limit in message`() {
+ val ex = ChannelLimitExceededException(10)
+ assertTrue(ex.message!!.contains("10"))
+ assertEquals(10, ex.limit)
+ }
+
+ // --- ChannelMemberAlreadyException ---
+
+ @Test
+ fun `ChannelMemberAlreadyException should contain playerId and channelId in message`() {
+ val ex = ChannelMemberAlreadyException(testPlayerId, testChannelId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertTrue(ex.message!!.contains(testChannelId))
+ assertEquals(testChannelId, ex.channelId)
+ }
+
+ // --- ChannelMemberLimitExceededException ---
+
+ @Test
+ fun `ChannelMemberLimitExceededException should contain channelId and limit in message`() {
+ val ex = ChannelMemberLimitExceededException(testChannelId, 50)
+ assertTrue(ex.message!!.contains(testChannelId))
+ assertTrue(ex.message!!.contains("50"))
+ assertEquals(testChannelId, ex.channelId)
+ assertEquals(50, ex.limit)
+ }
+
+ // --- ChannelMemberNotFoundException ---
+
+ @Test
+ fun `ChannelMemberNotFoundException should contain channelId in message`() {
+ val ex = ChannelMemberNotFoundException(testChannelId)
+ assertTrue(ex.message!!.contains(testChannelId))
+ }
+
+ // --- ChannelNoOwnerPermissionException ---
+
+ @Test
+ fun `ChannelNoOwnerPermissionException should contain ownerId in message`() {
+ val ex = ChannelNoOwnerPermissionException(testPlayerId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ }
+
+ // --- ChannelNotFoundException ---
+
+ @Test
+ fun `ChannelNotFoundException should contain channelId in message`() {
+ val ex = ChannelNotFoundException(testChannelId)
+ assertTrue(ex.message!!.contains(testChannelId))
+ }
+
+ // --- ChannelNotMemberException ---
+
+ @Test
+ fun `ChannelNotMemberException should contain playerId and channelId in message`() {
+ val ex = ChannelNotMemberException(testPlayerId, testChannelId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertTrue(ex.message!!.contains(testChannelId))
+ }
+
+ // --- ChannelPlayerAlreadyBannedException ---
+
+ @Test
+ fun `ChannelPlayerAlreadyBannedException should contain playerId and channelId in message`() {
+ val ex = ChannelPlayerAlreadyBannedException(testPlayerId, testChannelId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertTrue(ex.message!!.contains(testChannelId))
+ assertEquals(testPlayerId, ex.playerId)
+ assertEquals(testChannelId, ex.channelId)
+ }
+
+ // --- ChannelPlayerBannedException ---
+
+ @Test
+ fun `ChannelPlayerBannedException should contain playerId and channelId in message`() {
+ val ex = ChannelPlayerBannedException(testPlayerId, testChannelId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertTrue(ex.message!!.contains(testChannelId))
+ assertEquals(testChannelId, ex.channelId)
+ }
+
+ // --- ChannelPlayerBypassBanException ---
+
+ @Test
+ fun `ChannelPlayerBypassBanException should contain playerId in message and store channelId`() {
+ val ex = ChannelPlayerBypassBanException(testPlayerId, testChannelId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertEquals(testChannelId, ex.channelId)
+ }
+
+ // --- ChannelPlayerBypassKickException ---
+
+ @Test
+ fun `ChannelPlayerBypassKickException should contain playerId in message and store channelId`() {
+ val ex = ChannelPlayerBypassKickException(testPlayerId, testChannelId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertEquals(testChannelId, ex.channelId)
+ }
+
+ // --- ChannelPlayerMembershipLimitExceededException ---
+
+ @Test
+ fun `ChannelPlayerMembershipLimitExceededException should contain playerId and limit in message`() {
+ val ex = ChannelPlayerMembershipLimitExceededException(testPlayerId, 5)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertTrue(ex.message!!.contains("5"))
+ assertEquals(testPlayerId, ex.playerId)
+ assertEquals(5, ex.limit)
+ }
+
+ // --- ChannelPlayerNotBannedException ---
+
+ @Test
+ fun `ChannelPlayerNotBannedException should contain playerId and channelId in message`() {
+ val ex = ChannelPlayerNotBannedException(testPlayerId, testChannelId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertTrue(ex.message!!.contains(testChannelId))
+ assertEquals(testChannelId, ex.channelId)
+ }
+
+ // --- ChannelPrivateRequiresInvitationException ---
+
+ @Test
+ fun `ChannelPrivateRequiresInvitationException should contain playerId and channelId in message`() {
+ val ex = ChannelPrivateRequiresInvitationException(testPlayerId, testChannelId)
+ assertTrue(ex.message!!.contains(testPlayerId.toString()))
+ assertTrue(ex.message!!.contains(testChannelId))
+ assertEquals(testPlayerId, ex.playerId)
+ assertEquals(testChannelId, ex.channelId)
+ }
+
+ // --- ChannelRuntimeException ---
+
+ @Test
+ fun `ChannelRuntimeException should preserve message`() {
+ val ex = ChannelRuntimeException("runtime error")
+ assertEquals("runtime error", ex.message)
+ assertNull(ex.cause)
+ }
+
+ @Test
+ fun `ChannelRuntimeException should preserve cause`() {
+ val cause = RuntimeException("root cause")
+ val ex = ChannelRuntimeException("runtime error", cause)
+ assertEquals("runtime error", ex.message)
+ assertEquals(cause, ex.cause)
+ }
+
+ // --- ChannelStorageLoadException ---
+
+ @Test
+ fun `ChannelStorageLoadException should preserve message`() {
+ val ex = ChannelStorageLoadException("load error")
+ assertEquals("load error", ex.message)
+ assertNull(ex.cause)
+ }
+
+ @Test
+ fun `ChannelStorageLoadException should preserve cause`() {
+ val cause = RuntimeException("io error")
+ val ex = ChannelStorageLoadException("load error", cause)
+ assertEquals("load error", ex.message)
+ assertEquals(cause, ex.cause)
+ }
+
+ // --- ChannelStorageSaveException ---
+
+ @Test
+ fun `ChannelStorageSaveException should preserve message`() {
+ val ex = ChannelStorageSaveException("save error")
+ assertEquals("save error", ex.message)
+ assertNull(ex.cause)
+ }
+
+ @Test
+ fun `ChannelStorageSaveException should preserve cause`() {
+ val cause = RuntimeException("io error")
+ val ex = ChannelStorageSaveException("save error", cause)
+ assertEquals("save error", ex.message)
+ assertEquals(cause, ex.cause)
+ }
+
+ // --- ChatModeStorageException ---
+
+ @Test
+ fun `ChatModeStorageException should preserve message`() {
+ val ex = ChatModeStorageException("chatmode error")
+ assertEquals("chatmode error", ex.message)
+ assertNull(ex.cause)
+ }
+
+ @Test
+ fun `ChatModeStorageException should preserve cause`() {
+ val cause = RuntimeException("io error")
+ val ex = ChatModeStorageException("chatmode error", cause)
+ assertEquals("chatmode error", ex.message)
+ assertEquals(cause, ex.cause)
+ }
+
+ // --- RequirePermissionException ---
+
+ @Test
+ fun `RequirePermissionException should join permission nodes in message`() {
+ val permissions = listOf(LunaticChatPermissionNode.Lc, LunaticChatPermissionNode.Tell)
+ val ex = RequirePermissionException(permissions)
+ assertTrue(ex.message!!.contains("lunaticchat.command.lc"))
+ assertTrue(ex.message!!.contains("lunaticchat.command.tell"))
+ }
+
+ @Test
+ fun `RequirePermissionException with single permission should contain that node`() {
+ val permissions = listOf(LunaticChatPermissionNode.Spy)
+ val ex = RequirePermissionException(permissions)
+ assertTrue(ex.message!!.contains("lunaticchat.spy"))
+ }
+
+ // --- All exceptions are instances of Exception ---
+
+ @Test
+ fun `all exceptions should be instances of Exception`() {
+ assertIs<Exception>(ChannelAlreadyActiveException(testPlayerId, testChannelId))
+ assertIs<Exception>(ChannelCannotInviteSelfException(testPlayerId))
+ assertIs<Exception>(ChannelLimitExceededException(10))
+ assertIs<Exception>(ChannelMemberAlreadyException(testPlayerId, testChannelId))
+ assertIs<Exception>(ChannelMemberLimitExceededException(testChannelId, 50))
+ assertIs<Exception>(ChannelMemberNotFoundException(testChannelId))
+ assertIs<Exception>(ChannelNoOwnerPermissionException(testPlayerId))
+ assertIs<Exception>(ChannelNotFoundException(testChannelId))
+ assertIs<Exception>(ChannelNotMemberException(testPlayerId, testChannelId))
+ assertIs<Exception>(ChannelPlayerAlreadyBannedException(testPlayerId, testChannelId))
+ assertIs<Exception>(ChannelPlayerBannedException(testPlayerId, testChannelId))
+ assertIs<Exception>(ChannelPlayerBypassBanException(testPlayerId, testChannelId))
+ assertIs<Exception>(ChannelPlayerBypassKickException(testPlayerId, testChannelId))
+ assertIs<Exception>(ChannelPlayerMembershipLimitExceededException(testPlayerId, 5))
+ assertIs<Exception>(ChannelPlayerNotBannedException(testPlayerId, testChannelId))
+ assertIs<Exception>(ChannelPrivateRequiresInvitationException(testPlayerId, testChannelId))
+ assertIs<Exception>(ChannelRuntimeException("test"))
+ assertIs<Exception>(ChannelStorageLoadException("test"))
+ assertIs<Exception>(ChannelStorageSaveException("test"))
+ assertIs<Exception>(ChatModeStorageException("test"))
+ assertIs<Exception>(RequirePermissionException(listOf(LunaticChatPermissionNode.Lc)))
+ }
+}
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/permission/LunaticChatPermissionNodeTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/permission/LunaticChatPermissionNodeTest.kt
new file mode 100644
index 0000000..c9838f2
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/permission/LunaticChatPermissionNodeTest.kt
@@ -0,0 +1,82 @@
+package dev.m1sk9.lunaticChat.engine.permission
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class LunaticChatPermissionNodeTest {
+ private val allNodes =
+ listOf(
+ LunaticChatPermissionNode.Lc,
+ LunaticChatPermissionNode.Tell,
+ LunaticChatPermissionNode.Reply,
+ LunaticChatPermissionNode.Settings,
+ LunaticChatPermissionNode.Status,
+ LunaticChatPermissionNode.Channel,
+ LunaticChatPermissionNode.ChannelCreate,
+ LunaticChatPermissionNode.ChannelList,
+ LunaticChatPermissionNode.ChannelJoin,
+ LunaticChatPermissionNode.ChannelLeave,
+ LunaticChatPermissionNode.ChannelSwitch,
+ LunaticChatPermissionNode.ChannelStatus,
+ LunaticChatPermissionNode.ChannelInfo,
+ LunaticChatPermissionNode.ChannelDelete,
+ LunaticChatPermissionNode.ChannelInvite,
+ LunaticChatPermissionNode.ChannelKick,
+ LunaticChatPermissionNode.ChannelBan,
+ LunaticChatPermissionNode.ChannelUnban,
+ LunaticChatPermissionNode.ChannelMod,
+ LunaticChatPermissionNode.ChannelOwnership,
+ LunaticChatPermissionNode.ChatMode,
+ LunaticChatPermissionNode.ChatModeToggle,
+ LunaticChatPermissionNode.VelocityStatus,
+ LunaticChatPermissionNode.Spy,
+ LunaticChatPermissionNode.NoticeUpdate,
+ LunaticChatPermissionNode.ChannelBypass,
+ )
+
+ @Test
+ fun `all permission nodes should have lunaticchat prefix`() {
+ allNodes.forEach { node ->
+ assertTrue(
+ node.permissionNode.startsWith("lunaticchat."),
+ "Expected '${node.permissionNode}' to start with 'lunaticchat.'",
+ )
+ }
+ }
+
+ @Test
+ fun `should have 26 permission nodes`() {
+ assertEquals(26, allNodes.size)
+ }
+
+ @Test
+ fun `spot check specific permission node values`() {
+ assertEquals("lunaticchat.command.lc", LunaticChatPermissionNode.Lc.permissionNode)
+ assertEquals("lunaticchat.command.tell", LunaticChatPermissionNode.Tell.permissionNode)
+ assertEquals("lunaticchat.command.reply", LunaticChatPermissionNode.Reply.permissionNode)
+ assertEquals("lunaticchat.command.lc.settings", LunaticChatPermissionNode.Settings.permissionNode)
+ assertEquals("lunaticchat.command.lc.channel.create", LunaticChatPermissionNode.ChannelCreate.permissionNode)
+ assertEquals("lunaticchat.command.lc.chatmode.toggle", LunaticChatPermissionNode.ChatModeToggle.permissionNode)
+ assertEquals("lunaticchat.command.lcv.status", LunaticChatPermissionNode.VelocityStatus.permissionNode)
+ assertEquals("lunaticchat.spy", LunaticChatPermissionNode.Spy.permissionNode)
+ assertEquals("lunaticchat.noticeupdate", LunaticChatPermissionNode.NoticeUpdate.permissionNode)
+ assertEquals("lunaticchat.channelbypass", LunaticChatPermissionNode.ChannelBypass.permissionNode)
+ }
+
+ @Test
+ fun `all permission node strings should be non-empty`() {
+ allNodes.forEach { node ->
+ assertTrue(
+ node.permissionNode.isNotEmpty(),
+ "Permission node should not be empty",
+ )
+ }
+ }
+
+ @Test
+ fun `all permission node strings should be unique`() {
+ val nodeStrings = allNodes.map { it.permissionNode }
+ assertEquals(nodeStrings.size, nodeStrings.toSet().size, "All permission nodes should be unique")
+ }
+}
diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/SettingsDataClassesTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/SettingsDataClassesTest.kt
new file mode 100644
index 0000000..8743af9
--- /dev/null
+++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/SettingsDataClassesTest.kt
@@ -0,0 +1,167 @@
+package dev.m1sk9.lunaticChat.engine.settings
+
+import dev.m1sk9.lunaticChat.engine.chat.ChatMode
+import dev.m1sk9.lunaticChat.engine.chat.ChatModeData
+import dev.m1sk9.lunaticChat.engine.converter.CacheData
+import kotlinx.serialization.json.Json
+import java.util.UUID
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class SettingsDataClassesTest {
+ private val json = Json { ignoreUnknownKeys = true }
+ private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000")
+ private val testUUID2 = UUID.fromString("00000002-0000-0000-0000-000000000000")
+
+ // --- PlayerChatSettings ---
+
+ @Test
+ fun `PlayerChatSettings default values should all be true`() {
+ val settings = PlayerChatSettings(uuid = testUUID)
+ assertTrue(settings.japaneseConversionEnabled)
+ assertTrue(settings.directMessageNotificationEnabled)
+ assertTrue(settings.channelMessageNotificationEnabled)
+ }
+
+ @Test
+ fun `PlayerChatSettings serialization round-trip should preserve all fields`() {
+ val original =
+ PlayerChatSettings(
+ uuid = testUUID,
+ japaneseConversionEnabled = false,
+ directMessageNotificationEnabled = true,
+ channelMessageNotificationEnabled = false,
+ )
+
+ val serialized = json.encodeToString(PlayerChatSettings.serializer(), original)
+ val deserialized = json.decodeFromString(PlayerChatSettings.serializer(), serialized)
+
+ assertEquals(original.uuid, deserialized.uuid)
+ assertEquals(original.japaneseConversionEnabled, deserialized.japaneseConversionEnabled)
+ assertEquals(original.directMessageNotificationEnabled, deserialized.directMessageNotificationEnabled)
+ assertEquals(original.channelMessageNotificationEnabled, deserialized.channelMessageNotificationEnabled)
+ }
+
+ @Test
+ fun `PlayerChatSettings copy should allow flag toggle`() {
+ val original = PlayerChatSettings(uuid = testUUID)
+ val modified = original.copy(japaneseConversionEnabled = false)
+
+ assertFalse(modified.japaneseConversionEnabled)
+ assertTrue(modified.directMessageNotificationEnabled)
+ assertTrue(modified.channelMessageNotificationEnabled)
+ assertEquals(original.uuid, modified.uuid)
+ }
+
+ @Test
+ fun `PlayerChatSettings copy should allow toggling all flags`() {
+ val original = PlayerChatSettings(uuid = testUUID)
+ val modified =
+ original.copy(
+ japaneseConversionEnabled = false,
+ directMessageNotificationEnabled = false,
+ channelMessageNotificationEnabled = false,
+ )
+
+ assertFalse(modified.japaneseConversionEnabled)
+ assertFalse(modified.directMessageNotificationEnabled)
+ assertFalse(modified.channelMessageNotificationEnabled)
+ }
+
+ // --- PlayerSettingsData ---
+
+ @Test
+ fun `PlayerSettingsData default values should be correct`() {
+ val data = PlayerSettingsData()
+ assertEquals(1, data.version)
+ assertTrue(data.japaneseConversion.isEmpty())
+ assertTrue(data.directMessageNotification.isEmpty())
+ assertTrue(data.channelMessageNotification.isEmpty())
+ }
+
+ @Test
+ fun `PlayerSettingsData serialization round-trip should preserve UUID keys`() {
+ val original =
+ PlayerSettingsData(
+ version = 1,
+ japaneseConversion = mapOf(testUUID to true, testUUID2 to false),
+ directMessageNotification = mapOf(testUUID to false),
+ channelMessageNotification = mapOf(testUUID2 to true),
+ )
+
+ val serialized = json.encodeToString(PlayerSettingsData.serializer(), original)
+ val deserialized = json.decodeFromString(PlayerSettingsData.serializer(), serialized)
+
+ assertEquals(original.version, deserialized.version)
+ assertEquals(original.japaneseConversion, deserialized.japaneseConversion)
+ assertEquals(original.directMessageNotification, deserialized.directMessageNotification)
+ assertEquals(original.channelMessageNotification, deserialized.channelMessageNotification)
+ }
+
+ @Test
+ fun `PlayerSettingsData serialized UUID keys should be string format`() {
+ val data = PlayerSettingsData(japaneseConversion = mapOf(testUUID to true))
+ val serialized = json.encodeToString(PlayerSettingsData.serializer(), data)
+ assertTrue(serialized.contains(testUUID.toString()))
+ }
+
+ // --- CacheData ---
+
+ @Test
+ fun `CacheData should store version and entries`() {
+ val data = CacheData(version = "1.0", entries = mapOf("hello" to "こんにちは"))
+ assertEquals("1.0", data.version)
+ assertEquals("こんにちは", data.entries["hello"])
+ }
+
+ @Test
+ fun `CacheData serialization round-trip should preserve all fields`() {
+ val original =
+ CacheData(
+ version = "2.0",
+ entries = mapOf("hello" to "こんにちは", "world" to "世界"),
+ )
+
+ val serialized = json.encodeToString(CacheData.serializer(), original)
+ val deserialized = json.decodeFromString(CacheData.serializer(), serialized)
+
+ assertEquals(original.version, deserialized.version)
+ assertEquals(original.entries, deserialized.entries)
+ }
+
+ // --- ChatModeData ---
+
+ @Test
+ fun `ChatModeData default should have empty modes map`() {
+ val data = ChatModeData()
+ assertTrue(data.modes.isEmpty())
+ }
+
+ @Test
+ fun `ChatModeData serialization round-trip should preserve UUID-keyed map`() {
+ val original =
+ ChatModeData(
+ modes =
+ mapOf(
+ testUUID to ChatMode.CHANNEL,
+ testUUID2 to ChatMode.GLOBAL,
+ ),
+ )
+
+ val serialized = json.encodeToString(ChatModeData.serializer(), original)
+ val deserialized = json.decodeFromString(ChatModeData.serializer(), serialized)
+
+ assertEquals(original.modes.size, deserialized.modes.size)
+ assertEquals(ChatMode.CHANNEL, deserialized.modes[testUUID])
+ assertEquals(ChatMode.GLOBAL, deserialized.modes[testUUID2])
+ }
+
+ @Test
+ fun `ChatModeData serialized should contain UUID strings`() {
+ val data = ChatModeData(modes = mapOf(testUUID to ChatMode.GLOBAL))
+ val serialized = json.encodeToString(ChatModeData.serializer(), data)
+ assertTrue(serialized.contains(testUUID.toString()))
+ }
+}