summaryrefslogtreecommitdiff
path: root/platform-paper/src/test
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-01-27 22:09:22 +0900
committerSho Sakuma <me@m1sk9.dev>2026-01-27 22:33:43 +0900
commit5171dc41dc633e51470ea2e9f92b3434547a465c (patch)
tree046ac63c25077673bec55697c0cfb46857f77cb0 /platform-paper/src/test
parent21d17ca948193a9f4b7ef080d2b949240e64e538 (diff)
downloadLunaticChat-5171dc41dc633e51470ea2e9f92b3434547a465c.tar.gz
LunaticChat-5171dc41dc633e51470ea2e9f92b3434547a465c.tar.bz2
LunaticChat-5171dc41dc633e51470ea2e9f92b3434547a465c.zip
test: Add more test-case
Diffstat (limited to 'platform-paper/src/test')
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/TestUtils.kt208
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/ChatModeManagerTest.kt223
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt134
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt288
4 files changed, 853 insertions, 0 deletions
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/TestUtils.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/TestUtils.kt
new file mode 100644
index 0000000..66fdbd4
--- /dev/null
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/TestUtils.kt
@@ -0,0 +1,208 @@
+package dev.m1sk9.lunaticChat.paper
+
+import dev.m1sk9.lunaticChat.engine.chat.channel.Channel
+import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelMember
+import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole
+import dev.m1sk9.lunaticChat.engine.settings.PlayerChatSettings
+import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration
+import dev.m1sk9.lunaticChat.paper.config.key.ChannelChatFeatureConfig
+import dev.m1sk9.lunaticChat.paper.config.key.FeaturesConfig
+import dev.m1sk9.lunaticChat.paper.config.key.JapaneseConversionFeatureConfig
+import dev.m1sk9.lunaticChat.paper.config.key.MessageFormatConfig
+import dev.m1sk9.lunaticChat.paper.config.key.QuickRepliesFeatureConfig
+import dev.m1sk9.lunaticChat.paper.i18n.Language
+import io.mockk.mockk
+import org.bukkit.entity.Player
+import org.bukkit.plugin.java.JavaPlugin
+import java.util.UUID
+import java.util.logging.Logger
+
+/**
+ * Common test utilities for LunaticChat tests.
+ * Provides mock factories, test data builders, and assertion helpers.
+ */
+object TestUtils {
+ /**
+ * Creates a test logger that collects log messages for verification.
+ */
+ class TestLogger : Logger("test", null) {
+ val infoMessages = mutableListOf<String>()
+ val warningMessages = mutableListOf<String>()
+ val severeMessages = mutableListOf<String>()
+
+ override fun info(msg: String) {
+ infoMessages.add(msg)
+ }
+
+ override fun warning(msg: String) {
+ warningMessages.add(msg)
+ }
+
+ override fun severe(msg: String) {
+ severeMessages.add(msg)
+ }
+
+ fun clear() {
+ infoMessages.clear()
+ warningMessages.clear()
+ severeMessages.clear()
+ }
+ }
+
+ /**
+ * Creates a default test configuration with sensible defaults.
+ */
+ fun createTestConfiguration(
+ quickRepliesEnabled: Boolean = true,
+ japaneseConversionEnabled: Boolean = false,
+ channelChatEnabled: Boolean = false,
+ maxChannelsPerServer: Int = 10,
+ maxMembersPerChannel: Int = 50,
+ maxMembershipPerPlayer: Int = 5,
+ debug: Boolean = false,
+ checkForUpdates: Boolean = false,
+ language: Language = Language.EN,
+ ): LunaticChatConfiguration =
+ LunaticChatConfiguration(
+ features =
+ FeaturesConfig(
+ quickReplies = QuickRepliesFeatureConfig(enabled = quickRepliesEnabled),
+ japaneseConversion =
+ JapaneseConversionFeatureConfig(
+ enabled = japaneseConversionEnabled,
+ cacheMaxEntries = 500,
+ cacheSaveIntervalSeconds = 300,
+ cacheFilePath = "test-conversion-cache.json",
+ apiTimeout = 3000,
+ apiRetryAttempts = 2,
+ ),
+ channelChat =
+ ChannelChatFeatureConfig(
+ enabled = channelChatEnabled,
+ maxChannelsPerServer = maxChannelsPerServer,
+ maxMembersPerChannel = maxMembersPerChannel,
+ maxMembershipPerPlayer = maxMembershipPerPlayer,
+ ),
+ ),
+ messageFormat =
+ MessageFormatConfig(
+ directMessageFormat = "§7[§e{sender} §7>> §e{recipient}§7] §f{message}",
+ channelMessageFormat = "§7[§b#{channel}§7] §e{sender}: §f{message}",
+ ),
+ debug = debug,
+ checkForUpdates = checkForUpdates,
+ userSettingsFilePath = "test-player-settings.yaml",
+ language = language,
+ )
+
+ /**
+ * Creates a default player settings for testing.
+ */
+ fun createTestPlayerSettings(
+ uuid: UUID = UUID.randomUUID(),
+ japaneseConversionEnabled: Boolean = true,
+ directMessageNotificationEnabled: Boolean = true,
+ channelMessageNotificationEnabled: Boolean = true,
+ ): PlayerChatSettings =
+ PlayerChatSettings(
+ uuid = uuid,
+ japaneseConversionEnabled = japaneseConversionEnabled,
+ directMessageNotificationEnabled = directMessageNotificationEnabled,
+ channelMessageNotificationEnabled = channelMessageNotificationEnabled,
+ )
+
+ /**
+ * Creates a test channel with default values.
+ */
+ fun createTestChannel(
+ id: String = "test-channel-1",
+ name: String = "Test Channel",
+ description: String? = null,
+ ownerId: UUID = UUID.randomUUID(),
+ isPrivate: Boolean = false,
+ createdAt: Long = System.currentTimeMillis(),
+ bannedPlayers: Set<UUID> = emptySet(),
+ ): Channel =
+ Channel(
+ id = id,
+ name = name,
+ description = description,
+ ownerId = ownerId,
+ createdAt = createdAt,
+ isPrivate = isPrivate,
+ bannedPlayers = bannedPlayers,
+ )
+
+ /**
+ * Creates a test channel member.
+ */
+ fun createTestChannelMember(
+ channelId: String = "test-channel-1",
+ playerId: UUID = UUID.randomUUID(),
+ role: ChannelRole = ChannelRole.MEMBER,
+ joinedAt: Long = System.currentTimeMillis(),
+ ): ChannelMember =
+ ChannelMember(
+ channelId = channelId,
+ playerId = playerId,
+ role = role,
+ joinedAt = joinedAt,
+ )
+
+ /**
+ * Creates a mock Player with the given UUID and name.
+ */
+ fun createMockPlayer(
+ uuid: UUID = UUID.randomUUID(),
+ name: String = "TestPlayer",
+ isOnline: Boolean = true,
+ ): Player {
+ val player = mockk<Player>(relaxed = true)
+ io.mockk.every { player.uniqueId } returns uuid
+ io.mockk.every { player.name } returns name
+ io.mockk.every { player.isOnline } returns isOnline
+ return player
+ }
+
+ /**
+ * Creates a mock JavaPlugin for testing.
+ */
+ fun createMockPlugin(): JavaPlugin = mockk<JavaPlugin>(relaxed = true)
+
+ /**
+ * Creates a test UUID from an integer for deterministic testing.
+ */
+ fun createTestUUID(value: Int): UUID =
+ UUID.fromString(
+ String.format(
+ "%08x-0000-0000-0000-000000000000",
+ value,
+ ),
+ )
+
+ /**
+ * Assertion helper to check if a string contains all given substrings.
+ */
+ fun assertContainsAll(
+ actual: String,
+ vararg expected: String,
+ ) {
+ expected.forEach { substring ->
+ if (!actual.contains(substring)) {
+ throw AssertionError("Expected '$actual' to contain '$substring'")
+ }
+ }
+ }
+
+ /**
+ * Assertion helper to check if a list contains items matching a predicate.
+ */
+ fun <T> assertAny(
+ list: List<T>,
+ predicate: (T) -> Boolean,
+ ) {
+ if (!list.any(predicate)) {
+ throw AssertionError("Expected list to contain at least one matching item")
+ }
+ }
+}
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/ChatModeManagerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/ChatModeManagerTest.kt
new file mode 100644
index 0000000..d548f60
--- /dev/null
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/ChatModeManagerTest.kt
@@ -0,0 +1,223 @@
+package dev.m1sk9.lunaticChat.paper.chat
+
+import dev.m1sk9.lunaticChat.engine.chat.ChatMode
+import dev.m1sk9.lunaticChat.engine.chat.ChatModeData
+import dev.m1sk9.lunaticChat.paper.TestUtils
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import java.util.UUID
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+/**
+ * Tests for ChatModeManager.
+ * Verifies chat mode management, toggling, and persistence.
+ */
+class ChatModeManagerTest {
+ private fun createChatModeManager(
+ initialData: ChatModeData = ChatModeData(),
+ ): Triple<ChatModeManager, ChatModeStorage, TestUtils.TestLogger> {
+ val logger = TestUtils.TestLogger()
+ val storage = mockk<ChatModeStorage>(relaxed = true)
+
+ every { storage.loadFromDisk() } returns initialData
+
+ val manager = ChatModeManager(storage, logger)
+ return Triple(manager, storage, logger)
+ }
+
+ @Test
+ fun `initialize should load data from storage`() {
+ val playerId = UUID.randomUUID()
+ val initialData = ChatModeData(modes = mapOf(playerId to ChatMode.CHANNEL))
+ val (manager, _, logger) = createChatModeManager(initialData)
+
+ manager.initialize()
+
+ assertEquals(ChatMode.CHANNEL, manager.getChatMode(playerId))
+ assert(logger.infoMessages.any { it.contains("ChatModeManager initialized with 1 saved modes") })
+ }
+
+ @Test
+ fun `getChatMode should return DEFAULT for unknown player`() {
+ val (manager, _, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+ val mode = manager.getChatMode(playerId)
+
+ assertEquals(ChatMode.DEFAULT, mode)
+ assertEquals(ChatMode.GLOBAL, mode) // DEFAULT is GLOBAL
+ }
+
+ @Test
+ fun `getChatMode should return set mode`() {
+ val (manager, _, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+ manager.setChatMode(playerId, ChatMode.CHANNEL)
+
+ assertEquals(ChatMode.CHANNEL, manager.getChatMode(playerId))
+ }
+
+ @Test
+ fun `setChatMode should save to storage`() {
+ val (manager, storage, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+ manager.setChatMode(playerId, ChatMode.CHANNEL)
+
+ verify(exactly = 1) { storage.queueAsyncSave(any()) }
+ }
+
+ @Test
+ fun `toggleChatMode should switch from GLOBAL to CHANNEL`() {
+ val (manager, _, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+ // Initial mode is GLOBAL (default)
+ assertEquals(ChatMode.GLOBAL, manager.getChatMode(playerId))
+
+ val newMode = manager.toggleChatMode(playerId)
+
+ assertEquals(ChatMode.CHANNEL, newMode)
+ assertEquals(ChatMode.CHANNEL, manager.getChatMode(playerId))
+ }
+
+ @Test
+ fun `toggleChatMode should switch from CHANNEL to GLOBAL`() {
+ val (manager, _, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+ manager.setChatMode(playerId, ChatMode.CHANNEL)
+
+ val newMode = manager.toggleChatMode(playerId)
+
+ assertEquals(ChatMode.GLOBAL, newMode)
+ assertEquals(ChatMode.GLOBAL, manager.getChatMode(playerId))
+ }
+
+ @Test
+ fun `toggleChatMode should persist changes`() {
+ val (manager, storage, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+ manager.toggleChatMode(playerId)
+
+ verify(atLeast = 1) { storage.queueAsyncSave(any()) }
+ }
+
+ @Test
+ fun `removeChatMode should revert to default`() {
+ val (manager, _, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+ manager.setChatMode(playerId, ChatMode.CHANNEL)
+ assertEquals(ChatMode.CHANNEL, manager.getChatMode(playerId))
+
+ manager.removeChatMode(playerId)
+
+ assertEquals(ChatMode.GLOBAL, manager.getChatMode(playerId))
+ }
+
+ @Test
+ fun `removeChatMode should save to storage`() {
+ val (manager, storage, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+ manager.setChatMode(playerId, ChatMode.CHANNEL)
+ manager.removeChatMode(playerId)
+
+ verify(atLeast = 2) { storage.queueAsyncSave(any()) } // Once for set, once for remove
+ }
+
+ @Test
+ fun `saveToDisk should synchronously save`() {
+ val (manager, storage, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+ manager.setChatMode(playerId, ChatMode.CHANNEL)
+ manager.saveToDisk()
+
+ verify { storage.saveToDisk(any()) }
+ }
+
+ @Test
+ fun `shutdown should save and shutdown storage`() {
+ val (manager, storage, _) = createChatModeManager()
+ manager.initialize()
+
+ manager.shutdown()
+
+ verify { storage.saveToDisk(any()) }
+ verify { storage.shutdown() }
+ }
+
+ @Test
+ fun `manager should handle multiple players independently`() {
+ val (manager, _, _) = createChatModeManager()
+ manager.initialize()
+
+ val player1 = UUID.randomUUID()
+ val player2 = UUID.randomUUID()
+
+ manager.setChatMode(player1, ChatMode.CHANNEL)
+ manager.setChatMode(player2, ChatMode.GLOBAL)
+
+ assertEquals(ChatMode.CHANNEL, manager.getChatMode(player1))
+ assertEquals(ChatMode.GLOBAL, manager.getChatMode(player2))
+ }
+
+ @Test
+ fun `manager should support rapid mode changes`() {
+ val (manager, storage, _) = createChatModeManager()
+ manager.initialize()
+
+ val playerId = UUID.randomUUID()
+
+ // Toggle multiple times
+ manager.toggleChatMode(playerId) // GLOBAL -> CHANNEL
+ manager.toggleChatMode(playerId) // CHANNEL -> GLOBAL
+ manager.toggleChatMode(playerId) // GLOBAL -> CHANNEL
+
+ assertEquals(ChatMode.CHANNEL, manager.getChatMode(playerId))
+ verify(atLeast = 3) { storage.queueAsyncSave(any()) }
+ }
+
+ @Test
+ fun `manager should handle empty initial data`() {
+ val (manager, _, logger) = createChatModeManager(ChatModeData())
+ manager.initialize()
+
+ assert(logger.infoMessages.any { it.contains("ChatModeManager initialized with 0 saved modes") })
+ }
+
+ @Test
+ fun `manager should restore modes after initialization`() {
+ val player1 = TestUtils.createTestUUID(1)
+ val player2 = TestUtils.createTestUUID(2)
+ val initialData =
+ ChatModeData(
+ modes =
+ mapOf(
+ player1 to ChatMode.CHANNEL,
+ player2 to ChatMode.GLOBAL,
+ ),
+ )
+
+ val (manager, _, _) = createChatModeManager(initialData)
+ manager.initialize()
+
+ assertEquals(ChatMode.CHANNEL, manager.getChatMode(player1))
+ assertEquals(ChatMode.GLOBAL, manager.getChatMode(player2))
+ }
+}
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt
new file mode 100644
index 0000000..b07636b
--- /dev/null
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt
@@ -0,0 +1,134 @@
+package dev.m1sk9.lunaticChat.paper.chat.handler
+
+import dev.m1sk9.lunaticChat.paper.TestUtils
+import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter
+import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.mockk
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+/**
+ * Tests for DirectMessageHandler.
+ * Validates message handling with dependency injection and conversion features.
+ */
+class DirectMessageHandlerTest {
+ private fun createHandler(
+ configuration: dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration? = null,
+ settingsManager: PlayerSettingsManager? = null,
+ romanjiConverter: RomanjiConverter? = null,
+ ): DirectMessageHandler {
+ val config = configuration ?: TestUtils.createTestConfiguration()
+ return DirectMessageHandler(config, settingsManager, romanjiConverter)
+ }
+
+ @Test
+ fun `sendDirectMessage should return true on success`() {
+ val handler = createHandler()
+ val sender = TestUtils.createMockPlayer()
+ val recipient = TestUtils.createMockPlayer()
+
+ val result = handler.sendDirectMessage(sender, recipient, "Test message")
+
+ assertTrue(result)
+ }
+
+ @Test
+ fun `sendDirectMessage should use custom message format from configuration`() {
+ val customConfig =
+ TestUtils.createTestConfiguration().copy(
+ messageFormat =
+ TestUtils
+ .createTestConfiguration()
+ .messageFormat
+ .copy(
+ directMessageFormat = "[DM] {sender} -> {recipient}: {message}",
+ ),
+ )
+
+ val handler = createHandler(configuration = customConfig)
+ val sender = TestUtils.createMockPlayer(name = "Alice")
+ val recipient = TestUtils.createMockPlayer(name = "Bob")
+
+ val result = handler.sendDirectMessage(sender, recipient, "Test")
+
+ assertTrue(result)
+ }
+
+ @Test
+ fun `sendDirectMessage without conversion should send original message`() {
+ val settingsManager = mockk<PlayerSettingsManager>()
+ val senderSettings =
+ TestUtils.createTestPlayerSettings(
+ japaneseConversionEnabled = false,
+ )
+
+ every { settingsManager.getSettings(any()) } returns senderSettings
+
+ val handler = createHandler(settingsManager = settingsManager)
+ val sender = TestUtils.createMockPlayer()
+ val recipient = TestUtils.createMockPlayer()
+
+ val result = handler.sendDirectMessage(sender, recipient, "konnichiwa")
+
+ assertTrue(result)
+ }
+
+ @Test
+ fun `sendDirectMessage with conversion should handle conversion timeout gracefully`() {
+ val settingsManager = mockk<PlayerSettingsManager>()
+ val senderSettings =
+ TestUtils.createTestPlayerSettings(
+ japaneseConversionEnabled = true,
+ )
+ val romanjiConverter = mockk<RomanjiConverter>()
+
+ every { settingsManager.getSettings(any()) } returns senderSettings
+ // Simulate a slow conversion that would timeout
+ coEvery { romanjiConverter.convert(any()) } coAnswers {
+ kotlinx.coroutines.delay(2000) // Exceeds 1s timeout
+ "こんにちは"
+ }
+
+ val handler = createHandler(settingsManager = settingsManager, romanjiConverter = romanjiConverter)
+ val sender = TestUtils.createMockPlayer()
+ val recipient = TestUtils.createMockPlayer()
+
+ // Should not throw exception and should complete quickly (within timeout)
+ val result = handler.sendDirectMessage(sender, recipient, "konnichiwa")
+
+ assertTrue(result)
+ }
+
+ @Test
+ fun `handler can be created with injected configuration`() {
+ val config = TestUtils.createTestConfiguration(debug = true)
+ val handler = DirectMessageHandler(config, null, null)
+
+ // Handler should accept configuration via constructor (DI pattern)
+ // This validates Issue #1 refactoring - ConfigManager DI
+ val sender = TestUtils.createMockPlayer()
+ val recipient = TestUtils.createMockPlayer()
+ val result = handler.sendDirectMessage(sender, recipient, "Test")
+
+ assertTrue(result)
+ }
+
+ @Test
+ fun `handler can be created with all dependencies`() {
+ val config = TestUtils.createTestConfiguration()
+ val settingsManager = mockk<PlayerSettingsManager>(relaxed = true)
+ val romanjiConverter = mockk<RomanjiConverter>(relaxed = true)
+
+ every { settingsManager.getSettings(any()) } returns TestUtils.createTestPlayerSettings()
+
+ val handler = DirectMessageHandler(config, settingsManager, romanjiConverter)
+
+ val sender = TestUtils.createMockPlayer()
+ val recipient = TestUtils.createMockPlayer()
+ val result = handler.sendDirectMessage(sender, recipient, "Test")
+
+ assertTrue(result)
+ }
+}
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt
new file mode 100644
index 0000000..3f936f6
--- /dev/null
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt
@@ -0,0 +1,288 @@
+package dev.m1sk9.lunaticChat.paper.config
+
+import dev.m1sk9.lunaticChat.paper.i18n.Language
+import io.mockk.every
+import io.mockk.mockk
+import org.bukkit.configuration.file.FileConfiguration
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+/**
+ * Tests for ConfigManager to ensure proper configuration loading and parsing.
+ */
+class ConfigManagerTest {
+ private fun createMockConfig(values: Map<String, Any> = emptyMap()): FileConfiguration {
+ val config = mockk<FileConfiguration>(relaxed = true)
+
+ // Set up default values
+ every { config.getBoolean(any(), any()) } answers {
+ val key = firstArg<String>()
+ val default = secondArg<Boolean>()
+ (values[key] as? Boolean) ?: default
+ }
+
+ every { config.getInt(any(), any()) } answers {
+ val key = firstArg<String>()
+ val default = secondArg<Int>()
+ (values[key] as? Int) ?: default
+ }
+
+ every { config.getLong(any(), any()) } answers {
+ val key = firstArg<String>()
+ val default = secondArg<Long>()
+ (values[key] as? Long) ?: default
+ }
+
+ every { config.getString(any(), any()) } answers {
+ val key = firstArg<String>()
+ val default = secondArg<String>()
+ (values[key] as? String) ?: default
+ }
+
+ every { config.getString(any()) } answers {
+ val key = firstArg<String>()
+ values[key] as? String
+ }
+
+ return config
+ }
+
+ @Test
+ fun `loadConfiguration should load all default values`() {
+ val configManager = ConfigManager()
+ val mockConfig = createMockConfig()
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertNotNull(configuration)
+ assertTrue(configuration.features.quickReplies.enabled)
+ assertFalse(configuration.features.japaneseConversion.enabled)
+ assertFalse(configuration.features.channelChat.enabled)
+ assertFalse(configuration.debug)
+ assertFalse(configuration.checkForUpdates)
+ assertEquals(Language.EN, configuration.language)
+ }
+
+ @Test
+ fun `loadConfiguration should load quick replies configuration`() {
+ val configManager = ConfigManager()
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "features.quickReplies.enabled" to false,
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertFalse(configuration.features.quickReplies.enabled)
+ }
+
+ @Test
+ fun `loadConfiguration should load Japanese conversion configuration`() {
+ val configManager = ConfigManager()
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "features.japaneseConversion.enabled" to true,
+ "features.japaneseConversion.cache.maxEntries" to 1000,
+ "features.japaneseConversion.cache.saveIntervalSeconds" to 600,
+ "features.japaneseConversion.cache.filePath" to "custom_cache.json",
+ "features.japaneseConversion.api.timeout" to 5000L,
+ "features.japaneseConversion.api.retryAttempts" to 3,
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertTrue(configuration.features.japaneseConversion.enabled)
+ assertEquals(1000, configuration.features.japaneseConversion.cacheMaxEntries)
+ assertEquals(600, configuration.features.japaneseConversion.cacheSaveIntervalSeconds)
+ assertEquals("custom_cache.json", configuration.features.japaneseConversion.cacheFilePath)
+ assertEquals(5000L, configuration.features.japaneseConversion.apiTimeout)
+ assertEquals(3, configuration.features.japaneseConversion.apiRetryAttempts)
+ }
+
+ @Test
+ fun `loadConfiguration should load channel chat configuration`() {
+ val configManager = ConfigManager()
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "features.channelChat.enabled" to true,
+ "features.channelChat.maxChannelsPerServer" to 20,
+ "features.channelChat.maxMembersPerChannel" to 100,
+ "features.channelChat.maxMembershipPerPlayer" to 10,
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertTrue(configuration.features.channelChat.enabled)
+ assertEquals(20, configuration.features.channelChat.maxChannelsPerServer)
+ assertEquals(100, configuration.features.channelChat.maxMembersPerChannel)
+ assertEquals(10, configuration.features.channelChat.maxMembershipPerPlayer)
+ }
+
+ @Test
+ fun `loadConfiguration should load message format configuration`() {
+ val configManager = ConfigManager()
+ val customDMFormat = "DM: {sender} -> {recipient}: {message}"
+ val customChannelFormat = "[{channel}] {sender}: {message}"
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "messageFormat.directMessageFormat" to customDMFormat,
+ "messageFormat.channelMessageFormat" to customChannelFormat,
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertEquals(customDMFormat, configuration.messageFormat.directMessageFormat)
+ assertEquals(customChannelFormat, configuration.messageFormat.channelMessageFormat)
+ }
+
+ @Test
+ fun `loadConfiguration should load debug and update check settings`() {
+ val configManager = ConfigManager()
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "debug" to true,
+ "checkForUpdates" to true,
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertTrue(configuration.debug)
+ assertTrue(configuration.checkForUpdates)
+ }
+
+ @Test
+ fun `loadConfiguration should load custom settings file path`() {
+ val configManager = ConfigManager()
+ val customPath = "custom-player-settings.yaml"
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "userSettingsFilePath" to customPath,
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertEquals(customPath, configuration.userSettingsFilePath)
+ }
+
+ @Test
+ fun `loadConfiguration should load Japanese language`() {
+ val configManager = ConfigManager()
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "language" to "ja",
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertEquals(Language.JA, configuration.language)
+ }
+
+ @Test
+ fun `loadConfiguration should handle unknown language code`() {
+ val configManager = ConfigManager()
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "language" to "fr", // French not supported
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ // Should fall back to English
+ assertEquals(Language.EN, configuration.language)
+ }
+
+ @Test
+ fun `loadConfiguration should use default values when keys are missing`() {
+ val configManager = ConfigManager()
+ val mockConfig = createMockConfig(emptyMap())
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ // All features should have their defaults
+ assertTrue(configuration.features.quickReplies.enabled)
+ assertEquals(500, configuration.features.japaneseConversion.cacheMaxEntries)
+ assertEquals(300, configuration.features.japaneseConversion.cacheSaveIntervalSeconds)
+ assertEquals("conversion_cache.json", configuration.features.japaneseConversion.cacheFilePath)
+ assertEquals(3000L, configuration.features.japaneseConversion.apiTimeout)
+ assertEquals(2, configuration.features.japaneseConversion.apiRetryAttempts)
+ assertEquals(0, configuration.features.channelChat.maxChannelsPerServer)
+ assertEquals(0, configuration.features.channelChat.maxMembersPerChannel)
+ assertEquals(0, configuration.features.channelChat.maxMembershipPerPlayer)
+ }
+
+ @Test
+ fun `loadConfiguration can be called multiple times`() {
+ val configManager = ConfigManager()
+ val mockConfig1 =
+ createMockConfig(
+ mapOf("debug" to true),
+ )
+ val mockConfig2 =
+ createMockConfig(
+ mapOf("debug" to false),
+ )
+
+ val config1 = configManager.loadConfiguration(mockConfig1)
+ val config2 = configManager.loadConfiguration(mockConfig2)
+
+ assertTrue(config1.debug)
+ assertFalse(config2.debug)
+ }
+
+ @Test
+ fun `loadConfiguration should handle all features enabled`() {
+ val configManager = ConfigManager()
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "features.quickReplies.enabled" to true,
+ "features.japaneseConversion.enabled" to true,
+ "features.channelChat.enabled" to true,
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertTrue(configuration.features.quickReplies.enabled)
+ assertTrue(configuration.features.japaneseConversion.enabled)
+ assertTrue(configuration.features.channelChat.enabled)
+ }
+
+ @Test
+ fun `loadConfiguration should handle all features disabled`() {
+ val configManager = ConfigManager()
+ val mockConfig =
+ createMockConfig(
+ mapOf(
+ "features.quickReplies.enabled" to false,
+ "features.japaneseConversion.enabled" to false,
+ "features.channelChat.enabled" to false,
+ ),
+ )
+
+ val configuration = configManager.loadConfiguration(mockConfig)
+
+ assertFalse(configuration.features.quickReplies.enabled)
+ assertFalse(configuration.features.japaneseConversion.enabled)
+ assertFalse(configuration.features.channelChat.enabled)
+ }
+}