diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-02-25 22:38:19 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-02-25 22:38:44 +0900 |
| commit | f4bf20791f4470d3e9b865a53b610b5f277ebb82 (patch) | |
| tree | 7d0b3f9d3eff87730413290d4436b43ff6146b7a /platform-paper | |
| parent | 56ed04185036635dcfd2763391cd64b8be371b1a (diff) | |
| download | LunaticChat-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 'platform-paper')
4 files changed, 1106 insertions, 0 deletions
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManagerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManagerTest.kt new file mode 100644 index 0000000..acf610f --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManagerTest.kt @@ -0,0 +1,513 @@ +package dev.m1sk9.lunaticChat.paper.chat.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.ChannelLimitExceededException +import dev.m1sk9.lunaticChat.engine.exception.ChannelMemberLimitExceededException +import dev.m1sk9.lunaticChat.engine.exception.ChannelNoOwnerPermissionException +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerAlreadyBannedException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerNotBannedException +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.TestUtils.createTestChannel +import dev.m1sk9.lunaticChat.paper.TestUtils.createTestUUID +import dev.m1sk9.lunaticChat.paper.config.key.ChannelChatFeatureConfig +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class ChannelManagerTest { + private fun createManager( + initialData: ChannelData = ChannelData(), + maxChannelsPerServer: Int = 10, + maxMembersPerChannel: Int = 50, + ): Triple<ChannelManager, ChannelStorage, TestUtils.TestLogger> { + val logger = TestUtils.TestLogger() + val storage = mockk<ChannelStorage>(relaxed = true) + val config = + ChannelChatFeatureConfig( + enabled = true, + maxChannelsPerServer = maxChannelsPerServer, + maxMembersPerChannel = maxMembersPerChannel, + ) + + every { storage.loadFromDisk() } returns initialData + + val manager = ChannelManager(storage, logger, config) + return Triple(manager, storage, logger) + } + + @Test + fun `initialize should load channels from storage`() { + val ownerId = createTestUUID(1) + val channel = createTestChannel(id = "test-ch", name = "Test", ownerId = ownerId) + val members = + listOf( + ChannelMember(channelId = "test-ch", playerId = ownerId, role = ChannelRole.OWNER), + ) + val data = + ChannelData( + channels = mapOf("test-ch" to channel), + members = mapOf("test-ch" to members), + ) + + val (manager, _, logger) = createManager(initialData = data) + manager.initialize() + + val result = manager.getChannel("test-ch") + assertTrue(result.isSuccess) + assertEquals("test-ch", result.getOrThrow().id) + assertTrue(logger.infoMessages.any { it.contains("1 channels") }) + } + + @Test + fun `createChannel should succeed for new channel`() { + val (manager, storage, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val channel = createTestChannel(id = "new-ch", name = "New Channel", ownerId = ownerId) + + val result = manager.createChannel(channel) + + assertTrue(result.isSuccess) + assertEquals("new-ch", result.getOrThrow().id) + verify(atLeast = 1) { storage.queueAsyncSave(any()) } + } + + @Test + fun `createChannel should auto-add owner as member`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val channel = createTestChannel(id = "new-ch", name = "New Channel", ownerId = ownerId) + manager.createChannel(channel) + + val members = manager.getChannelMembers("new-ch") + assertTrue(members.isSuccess) + assertEquals(1, members.getOrThrow().size) + assertEquals(ownerId, members.getOrThrow()[0].playerId) + assertEquals(ChannelRole.OWNER, members.getOrThrow()[0].role) + } + + @Test + fun `createChannel should set owner active channel`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val channel = createTestChannel(id = "new-ch", name = "New Channel", ownerId = ownerId) + manager.createChannel(channel) + + assertEquals("new-ch", manager.getPlayerChannel(ownerId)) + } + + @Test + fun `createChannel should fail for duplicate ID`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val channel = createTestChannel(id = "dup-ch", name = "Channel", ownerId = ownerId) + manager.createChannel(channel) + + val result = manager.createChannel(channel) + assertTrue(result.isFailure) + assertIs<ChannelNotFoundException>(result.exceptionOrNull()) + } + + @Test + fun `createChannel should fail when limit reached`() { + val (manager, _, _) = createManager(maxChannelsPerServer = 1) + manager.initialize() + + val owner1 = createTestUUID(1) + val owner2 = createTestUUID(2) + manager.createChannel(createTestChannel(id = "ch1", name = "Ch1", ownerId = owner1)) + + val result = manager.createChannel(createTestChannel(id = "ch2", name = "Ch2", ownerId = owner2)) + assertTrue(result.isFailure) + assertIs<ChannelLimitExceededException>(result.exceptionOrNull()) + } + + @Test + fun `createChannel with unlimited limit should allow many channels`() { + val (manager, _, _) = createManager(maxChannelsPerServer = 0) + manager.initialize() + + repeat(5) { i -> + val result = + manager.createChannel( + createTestChannel(id = "ch-$i", name = "Channel $i", ownerId = createTestUUID(i)), + ) + assertTrue(result.isSuccess) + } + } + + @Test + fun `deleteChannel should succeed for owner`() { + val (manager, storage, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + manager.createChannel(createTestChannel(id = "del-ch", name = "To Delete", ownerId = ownerId)) + + val result = manager.deleteChannel("del-ch", ownerId) + assertTrue(result.isSuccess) + + val getResult = manager.getChannel("del-ch") + assertTrue(getResult.isFailure) + } + + @Test + fun `deleteChannel should fail for non-owner without bypass`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val otherId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "del-ch", name = "Channel", ownerId = ownerId)) + + val result = manager.deleteChannel("del-ch", otherId) + assertTrue(result.isFailure) + assertIs<ChannelNoOwnerPermissionException>(result.exceptionOrNull()) + } + + @Test + fun `deleteChannel should succeed for non-owner with bypass permission`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val adminId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "del-ch", name = "Channel", ownerId = ownerId)) + + val result = manager.deleteChannel("del-ch", adminId, hasBypassPermission = true) + assertTrue(result.isSuccess) + } + + @Test + fun `deleteChannel should fail for nonexistent channel`() { + val (manager, _, _) = createManager() + manager.initialize() + + val result = manager.deleteChannel("nonexistent", createTestUUID(1)) + assertTrue(result.isFailure) + assertIs<ChannelNotFoundException>(result.exceptionOrNull()) + } + + @Test + fun `deleteChannel should clear active channels for affected players`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + manager.createChannel(createTestChannel(id = "del-ch", name = "Channel", ownerId = ownerId)) + assertEquals("del-ch", manager.getPlayerChannel(ownerId)) + + manager.deleteChannel("del-ch", ownerId) + assertNull(manager.getPlayerChannel(ownerId)) + } + + @Test + fun `getChannel should return channel when exists`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + manager.createChannel(createTestChannel(id = "get-ch", name = "Get Channel", ownerId = ownerId)) + + val result = manager.getChannel("get-ch") + assertTrue(result.isSuccess) + assertEquals("Get Channel", result.getOrThrow().name) + } + + @Test + fun `getChannel should fail for nonexistent channel`() { + val (manager, _, _) = createManager() + manager.initialize() + + val result = manager.getChannel("nonexistent") + assertTrue(result.isFailure) + assertIs<ChannelNotFoundException>(result.exceptionOrNull()) + } + + @Test + fun `getAllChannels should return all channels`() { + val (manager, _, _) = createManager() + manager.initialize() + + manager.createChannel(createTestChannel(id = "ch1", name = "Ch1", ownerId = createTestUUID(1))) + manager.createChannel(createTestChannel(id = "ch2", name = "Ch2", ownerId = createTestUUID(2))) + + val result = manager.getAllChannels() + assertTrue(result.isSuccess) + assertEquals(2, result.getOrThrow().size) + } + + @Test + fun `getPublicChannels should exclude private channels`() { + val (manager, _, _) = createManager() + manager.initialize() + + manager.createChannel(createTestChannel(id = "public-ch", name = "Public", ownerId = createTestUUID(1))) + manager.createChannel( + createTestChannel(id = "private-ch", name = "Private", ownerId = createTestUUID(2), isPrivate = true), + ) + + val result = manager.getPublicChannels() + assertTrue(result.isSuccess) + assertEquals(1, result.getOrThrow().size) + assertEquals("public-ch", result.getOrThrow()[0].id) + } + + @Test + fun `addMember should add member to channel`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val memberId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "mem-ch", name = "Channel", ownerId = ownerId)) + + val result = manager.addMember("mem-ch", memberId, ChannelRole.MEMBER) + assertTrue(result.isSuccess) + + val members = manager.getChannelMembers("mem-ch").getOrThrow() + assertEquals(2, members.size) + assertTrue(members.any { it.playerId == memberId }) + } + + @Test + fun `addMember should fail when member limit reached`() { + val (manager, _, _) = createManager(maxMembersPerChannel = 1) + manager.initialize() + + val ownerId = createTestUUID(1) + manager.createChannel(createTestChannel(id = "lim-ch", name = "Channel", ownerId = ownerId)) + + val result = manager.addMember("lim-ch", createTestUUID(2), ChannelRole.MEMBER) + assertTrue(result.isFailure) + assertIs<ChannelMemberLimitExceededException>(result.exceptionOrNull()) + } + + @Test + fun `addMember should fail for nonexistent channel`() { + val (manager, _, _) = createManager() + manager.initialize() + + val result = manager.addMember("nonexistent", createTestUUID(1), ChannelRole.MEMBER) + assertTrue(result.isFailure) + assertIs<ChannelNotFoundException>(result.exceptionOrNull()) + } + + @Test + fun `removeMember should remove member from channel`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val memberId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "rem-ch", name = "Channel", ownerId = ownerId)) + manager.addMember("rem-ch", memberId, ChannelRole.MEMBER) + + val result = manager.removeMember("rem-ch", memberId) + assertTrue(result.isSuccess) + + val members = manager.getChannelMembers("rem-ch").getOrThrow() + assertFalse(members.any { it.playerId == memberId }) + } + + @Test + fun `banPlayer should add to banned list and remove from members`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val bannedId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "ban-ch", name = "Channel", ownerId = ownerId)) + manager.addMember("ban-ch", bannedId, ChannelRole.MEMBER) + + val result = manager.banPlayer("ban-ch", bannedId) + assertTrue(result.isSuccess) + assertTrue(result.getOrThrow().bannedPlayers.contains(bannedId)) + + val members = manager.getChannelMembers("ban-ch").getOrThrow() + assertFalse(members.any { it.playerId == bannedId }) + } + + @Test + fun `banPlayer should clear active channel for banned player`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val bannedId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "ban-ch", name = "Channel", ownerId = ownerId)) + manager.addMember("ban-ch", bannedId, ChannelRole.MEMBER) + manager.setPlayerChannel(bannedId, "ban-ch") + + manager.banPlayer("ban-ch", bannedId) + assertNull(manager.getPlayerChannel(bannedId)) + } + + @Test + fun `banPlayer should fail if already banned`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val bannedId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "ban-ch", name = "Channel", ownerId = ownerId)) + manager.banPlayer("ban-ch", bannedId) + + val result = manager.banPlayer("ban-ch", bannedId) + assertTrue(result.isFailure) + assertIs<ChannelPlayerAlreadyBannedException>(result.exceptionOrNull()) + } + + @Test + fun `unbanPlayer should remove from banned list`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val bannedId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "unban-ch", name = "Channel", ownerId = ownerId)) + manager.banPlayer("unban-ch", bannedId) + + val result = manager.unbanPlayer("unban-ch", bannedId) + assertTrue(result.isSuccess) + assertFalse(result.getOrThrow().bannedPlayers.contains(bannedId)) + } + + @Test + fun `unbanPlayer should fail if not banned`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + manager.createChannel(createTestChannel(id = "unban-ch", name = "Channel", ownerId = ownerId)) + + val result = manager.unbanPlayer("unban-ch", createTestUUID(2)) + assertTrue(result.isFailure) + assertIs<ChannelPlayerNotBannedException>(result.exceptionOrNull()) + } + + @Test + fun `isPlayerBanned should return true for banned player`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val bannedId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "ban-ch", name = "Channel", ownerId = ownerId)) + manager.banPlayer("ban-ch", bannedId) + + assertTrue(manager.isPlayerBanned("ban-ch", bannedId).getOrThrow()) + } + + @Test + fun `isPlayerBanned should return false for non-banned player`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + manager.createChannel(createTestChannel(id = "ban-ch", name = "Channel", ownerId = ownerId)) + + assertFalse(manager.isPlayerBanned("ban-ch", createTestUUID(2)).getOrThrow()) + } + + @Test + fun `updateMemberRole should change member role`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + val memberId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "role-ch", name = "Channel", ownerId = ownerId)) + manager.addMember("role-ch", memberId, ChannelRole.MEMBER) + + val result = manager.updateMemberRole("role-ch", memberId, ChannelRole.MODERATOR) + assertTrue(result.isSuccess) + + val members = manager.getChannelMembers("role-ch").getOrThrow() + val updatedMember = members.find { it.playerId == memberId } + assertNotNull(updatedMember) + assertEquals(ChannelRole.MODERATOR, updatedMember.role) + } + + @Test + fun `updateChannelOwner should transfer ownership`() { + val (manager, _, _) = createManager() + manager.initialize() + + val oldOwnerId = createTestUUID(1) + val newOwnerId = createTestUUID(2) + manager.createChannel(createTestChannel(id = "own-ch", name = "Channel", ownerId = oldOwnerId)) + manager.addMember("own-ch", newOwnerId, ChannelRole.MEMBER) + + val result = manager.updateChannelOwner("own-ch", newOwnerId) + assertTrue(result.isSuccess) + assertEquals(newOwnerId, result.getOrThrow().ownerId) + + val members = manager.getChannelMembers("own-ch").getOrThrow() + assertEquals(ChannelRole.OWNER, members.find { it.playerId == newOwnerId }?.role) + assertEquals(ChannelRole.MODERATOR, members.find { it.playerId == oldOwnerId }?.role) + } + + @Test + fun `setPlayerChannel should set and clear active channel`() { + val (manager, _, _) = createManager() + manager.initialize() + + val playerId = createTestUUID(1) + + manager.setPlayerChannel(playerId, "test-ch") + assertEquals("test-ch", manager.getPlayerChannel(playerId)) + + manager.setPlayerChannel(playerId, null) + assertNull(manager.getPlayerChannel(playerId)) + } + + @Test + fun `getPlayerChannelContext should return context for active channel`() { + val (manager, _, _) = createManager() + manager.initialize() + + val ownerId = createTestUUID(1) + manager.createChannel(createTestChannel(id = "ctx-ch", name = "Context Channel", ownerId = ownerId)) + + val context = manager.getPlayerChannelContext(ownerId) + assertNotNull(context) + assertEquals("ctx-ch", context.channelId) + assertEquals("Context Channel", context.channel.name) + } + + @Test + fun `getPlayerChannelContext should return null for no active channel`() { + val (manager, _, _) = createManager() + manager.initialize() + + assertNull(manager.getPlayerChannelContext(createTestUUID(99))) + } + + @Test + fun `saveToDisk should call storage saveToDisk`() { + val (manager, storage, _) = createManager() + manager.initialize() + + manager.saveToDisk() + + verify { storage.saveToDisk(any()) } + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMembershipManagerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMembershipManagerTest.kt new file mode 100644 index 0000000..1fcc39b --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMembershipManagerTest.kt @@ -0,0 +1,384 @@ +package dev.m1sk9.lunaticChat.paper.chat.channel + +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelData +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.exception.ChannelAlreadyActiveException +import dev.m1sk9.lunaticChat.engine.exception.ChannelMemberAlreadyException +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotMemberException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerBannedException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerMembershipLimitExceededException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPrivateRequiresInvitationException +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.TestUtils.createTestChannel +import dev.m1sk9.lunaticChat.paper.TestUtils.createTestUUID +import dev.m1sk9.lunaticChat.paper.config.key.ChannelChatFeatureConfig +import io.mockk.every +import io.mockk.mockk +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertIs +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class ChannelMembershipManagerTest { + private fun createManagers( + initialData: ChannelData = ChannelData(), + maxChannelsPerServer: Int = 10, + maxMembersPerChannel: Int = 50, + maxMembershipPerPlayer: Int = 5, + ): Triple<ChannelMembershipManager, ChannelManager, TestUtils.TestLogger> { + val logger = TestUtils.TestLogger() + val storage = mockk<ChannelStorage>(relaxed = true) + val config = + ChannelChatFeatureConfig( + enabled = true, + maxChannelsPerServer = maxChannelsPerServer, + maxMembersPerChannel = maxMembersPerChannel, + maxMembershipPerPlayer = maxMembershipPerPlayer, + ) + + every { storage.loadFromDisk() } returns initialData + + val channelManager = ChannelManager(storage, logger, config) + channelManager.initialize() + + val membershipManager = ChannelMembershipManager(channelManager, logger, config) + return Triple(membershipManager, channelManager, logger) + } + + @Test + fun `joinChannel should succeed for public channel`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val playerId = createTestUUID(2) + channelManager.createChannel(createTestChannel(id = "pub-ch", name = "Public", ownerId = ownerId)) + + val result = membership.joinChannel(playerId, "pub-ch") + assertTrue(result.isSuccess) + } + + @Test + fun `joinChannel should set channel as active`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val playerId = createTestUUID(2) + channelManager.createChannel(createTestChannel(id = "pub-ch", name = "Public", ownerId = ownerId)) + + membership.joinChannel(playerId, "pub-ch") + assertEquals("pub-ch", channelManager.getPlayerChannel(playerId)) + } + + @Test + fun `joinChannel should fail for nonexistent channel`() { + val (membership, _, _) = createManagers() + + val result = membership.joinChannel(createTestUUID(1), "nonexistent") + assertTrue(result.isFailure) + assertIs<ChannelNotFoundException>(result.exceptionOrNull()) + } + + @Test + fun `joinChannel should fail if channel is already active`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + channelManager.createChannel(createTestChannel(id = "active-ch", name = "Active", ownerId = ownerId)) + + // Owner already has this as active channel after creation + val result = membership.joinChannel(ownerId, "active-ch") + assertTrue(result.isFailure) + assertIs<ChannelAlreadyActiveException>(result.exceptionOrNull()) + } + + @Test + fun `joinChannel should fail if player is banned`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val bannedId = createTestUUID(2) + channelManager.createChannel(createTestChannel(id = "ban-ch", name = "Channel", ownerId = ownerId)) + channelManager.banPlayer("ban-ch", bannedId) + + val result = membership.joinChannel(bannedId, "ban-ch") + assertTrue(result.isFailure) + assertIs<ChannelPlayerBannedException>(result.exceptionOrNull()) + } + + @Test + fun `joinChannel should fail for private channel without bypass`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val playerId = createTestUUID(2) + channelManager.createChannel( + createTestChannel(id = "priv-ch", name = "Private", ownerId = ownerId, isPrivate = true), + ) + + val result = membership.joinChannel(playerId, "priv-ch") + assertTrue(result.isFailure) + assertIs<ChannelPrivateRequiresInvitationException>(result.exceptionOrNull()) + } + + @Test + fun `joinChannel should succeed for private channel with bypass`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val playerId = createTestUUID(2) + channelManager.createChannel( + createTestChannel(id = "priv-ch", name = "Private", ownerId = ownerId, isPrivate = true), + ) + + val result = membership.joinChannel(playerId, "priv-ch", bypassPrivateCheck = true) + assertTrue(result.isSuccess) + } + + @Test + fun `joinChannel should fail if already a member`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val playerId = createTestUUID(2) + channelManager.createChannel(createTestChannel(id = "mem-ch", name = "Channel", ownerId = ownerId)) + membership.joinChannel(playerId, "mem-ch") + + // Switch active to another channel first so we can try joining again + channelManager.createChannel(createTestChannel(id = "other-ch", name = "Other", ownerId = createTestUUID(3))) + channelManager.setPlayerChannel(playerId, "other-ch") + + val result = membership.joinChannel(playerId, "mem-ch") + assertTrue(result.isFailure) + assertIs<ChannelMemberAlreadyException>(result.exceptionOrNull()) + } + + @Test + fun `joinChannel should fail when membership limit reached`() { + val (membership, channelManager, _) = createManagers(maxMembershipPerPlayer = 1) + + val ownerId1 = createTestUUID(1) + val ownerId2 = createTestUUID(2) + val playerId = createTestUUID(3) + + channelManager.createChannel(createTestChannel(id = "ch1", name = "Ch1", ownerId = ownerId1)) + channelManager.createChannel(createTestChannel(id = "ch2", name = "Ch2", ownerId = ownerId2)) + + membership.joinChannel(playerId, "ch1") + + // Clear active channel to try joining second + channelManager.setPlayerChannel(playerId, null) + + val result = membership.joinChannel(playerId, "ch2") + assertTrue(result.isFailure) + assertIs<ChannelPlayerMembershipLimitExceededException>(result.exceptionOrNull()) + } + + @Test + fun `leaveChannel should remove from membership and clear active`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val playerId = createTestUUID(2) + channelManager.createChannel(createTestChannel(id = "leave-ch", name = "Channel", ownerId = ownerId)) + membership.joinChannel(playerId, "leave-ch") + + val result = membership.leaveChannel(playerId) + assertTrue(result.isSuccess) + assertNull(channelManager.getPlayerChannel(playerId)) + } + + @Test + fun `leaveChannel should fail if no active channel`() { + val (membership, _, _) = createManagers() + + val result = membership.leaveChannel(createTestUUID(99)) + assertTrue(result.isFailure) + assertIs<ChannelNotMemberException>(result.exceptionOrNull()) + } + + @Test + fun `switchChannel should switch to another member channel`() { + val (membership, channelManager, _) = createManagers() + + val ownerId1 = createTestUUID(1) + val ownerId2 = createTestUUID(2) + val playerId = createTestUUID(3) + + channelManager.createChannel(createTestChannel(id = "ch1", name = "Ch1", ownerId = ownerId1)) + channelManager.createChannel(createTestChannel(id = "ch2", name = "Ch2", ownerId = ownerId2)) + + membership.joinChannel(playerId, "ch1") + // Now switch active to ch2 first by joining + channelManager.setPlayerChannel(playerId, null) + membership.joinChannel(playerId, "ch2") + + // Switch back to ch1 + val result = membership.switchChannel(playerId, "ch1") + assertTrue(result.isSuccess) + assertEquals("ch1", channelManager.getPlayerChannel(playerId)) + } + + @Test + fun `switchChannel should fail for nonexistent channel`() { + val (membership, _, _) = createManagers() + + val result = membership.switchChannel(createTestUUID(1), "nonexistent") + assertTrue(result.isFailure) + assertIs<ChannelNotFoundException>(result.exceptionOrNull()) + } + + @Test + fun `switchChannel should fail if already active`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val playerId = createTestUUID(2) + channelManager.createChannel(createTestChannel(id = "sw-ch", name = "Channel", ownerId = ownerId)) + membership.joinChannel(playerId, "sw-ch") + + val result = membership.switchChannel(playerId, "sw-ch") + assertTrue(result.isFailure) + assertIs<ChannelAlreadyActiveException>(result.exceptionOrNull()) + } + + @Test + fun `switchChannel should fail if not a member`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val playerId = createTestUUID(2) + channelManager.createChannel(createTestChannel(id = "sw-ch", name = "Channel", ownerId = ownerId)) + + val result = membership.switchChannel(playerId, "sw-ch") + assertTrue(result.isFailure) + assertIs<ChannelNotMemberException>(result.exceptionOrNull()) + } + + @Test + fun `isMember should return true for members`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + channelManager.createChannel(createTestChannel(id = "mem-ch", name = "Channel", ownerId = ownerId)) + + assertTrue(membership.isMember(ownerId, "mem-ch").getOrThrow()) + } + + @Test + fun `isMember should return false for non-members`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + channelManager.createChannel(createTestChannel(id = "mem-ch", name = "Channel", ownerId = ownerId)) + + assertFalse(membership.isMember(createTestUUID(2), "mem-ch").getOrThrow()) + } + + @Test + fun `getMemberRole should return role for member`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + channelManager.createChannel(createTestChannel(id = "role-ch", name = "Channel", ownerId = ownerId)) + + assertEquals(ChannelRole.OWNER, membership.getMemberRole(ownerId, "role-ch").getOrThrow()) + } + + @Test + fun `getMemberRole should fail for non-member`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + channelManager.createChannel(createTestChannel(id = "role-ch", name = "Channel", ownerId = ownerId)) + + val result = membership.getMemberRole(createTestUUID(2), "role-ch") + assertTrue(result.isFailure) + assertIs<ChannelNotMemberException>(result.exceptionOrNull()) + } + + @Test + fun `getMemberRoleOrNull should return null for non-member`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + channelManager.createChannel(createTestChannel(id = "role-ch", name = "Channel", ownerId = ownerId)) + + assertNull(membership.getMemberRoleOrNull(createTestUUID(2), "role-ch")) + } + + @Test + fun `hasRole OWNER should return true only for owner`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val memberId = createTestUUID(2) + channelManager.createChannel(createTestChannel(id = "has-ch", name = "Channel", ownerId = ownerId)) + channelManager.addMember("has-ch", memberId, ChannelRole.MEMBER) + + assertTrue(membership.hasRole(ownerId, "has-ch", ChannelRole.OWNER).getOrThrow()) + assertFalse(membership.hasRole(memberId, "has-ch", ChannelRole.OWNER).getOrThrow()) + } + + @Test + fun `hasRole MODERATOR should return true for moderator and owner`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val modId = createTestUUID(2) + val memberId = createTestUUID(3) + channelManager.createChannel(createTestChannel(id = "has-ch", name = "Channel", ownerId = ownerId)) + channelManager.addMember("has-ch", modId, ChannelRole.MODERATOR) + channelManager.addMember("has-ch", memberId, ChannelRole.MEMBER) + + assertTrue(membership.hasRole(ownerId, "has-ch", ChannelRole.MODERATOR).getOrThrow()) + assertTrue(membership.hasRole(modId, "has-ch", ChannelRole.MODERATOR).getOrThrow()) + assertFalse(membership.hasRole(memberId, "has-ch", ChannelRole.MODERATOR).getOrThrow()) + } + + @Test + fun `hasRole MEMBER should return true for all members`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + val memberId = createTestUUID(2) + channelManager.createChannel(createTestChannel(id = "has-ch", name = "Channel", ownerId = ownerId)) + channelManager.addMember("has-ch", memberId, ChannelRole.MEMBER) + + assertTrue(membership.hasRole(ownerId, "has-ch", ChannelRole.MEMBER).getOrThrow()) + assertTrue(membership.hasRole(memberId, "has-ch", ChannelRole.MEMBER).getOrThrow()) + } + + @Test + fun `hasRole should return false for non-member`() { + val (membership, channelManager, _) = createManagers() + + val ownerId = createTestUUID(1) + channelManager.createChannel(createTestChannel(id = "has-ch", name = "Channel", ownerId = ownerId)) + + assertFalse(membership.hasRole(createTestUUID(99), "has-ch", ChannelRole.MEMBER).getOrThrow()) + } + + @Test + fun `getPlayerChannels should return all channels player is member of`() { + val (membership, channelManager, _) = createManagers() + + val ownerId1 = createTestUUID(1) + val ownerId2 = createTestUUID(2) + val playerId = createTestUUID(3) + + channelManager.createChannel(createTestChannel(id = "ch1", name = "Ch1", ownerId = ownerId1)) + channelManager.createChannel(createTestChannel(id = "ch2", name = "Ch2", ownerId = ownerId2)) + + membership.joinChannel(playerId, "ch1") + channelManager.setPlayerChannel(playerId, null) + membership.joinChannel(playerId, "ch2") + + val channels = membership.getPlayerChannels(playerId).getOrThrow() + assertEquals(2, channels.size) + assertTrue(channels.contains("ch1")) + assertTrue(channels.contains("ch2")) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/setting/SettingKeyTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/setting/SettingKeyTest.kt new file mode 100644 index 0000000..635d7e1 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/setting/SettingKeyTest.kt @@ -0,0 +1,50 @@ +package dev.m1sk9.lunaticChat.paper.command.setting + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNull + +class SettingKeyTest { + @Test + fun `fromString should resolve japanese key`() { + val result = SettingKey.fromString("japanese") + assertIs<SettingKey.Japanese>(result) + } + + @Test + fun `fromString should resolve notice key`() { + val result = SettingKey.fromString("notice") + assertIs<SettingKey.Notice>(result) + } + + @Test + fun `fromString should resolve chNotice key`() { + val result = SettingKey.fromString("chNotice") + assertIs<SettingKey.ChNotice>(result) + } + + @Test + fun `fromString should be case insensitive`() { + assertIs<SettingKey.Japanese>(SettingKey.fromString("JAPANESE")) + assertIs<SettingKey.Notice>(SettingKey.fromString("NOTICE")) + assertIs<SettingKey.ChNotice>(SettingKey.fromString("CHNOTICE")) + assertIs<SettingKey.Japanese>(SettingKey.fromString("Japanese")) + } + + @Test + fun `fromString should return null for unknown key`() { + assertNull(SettingKey.fromString("unknown")) + assertNull(SettingKey.fromString("")) + assertNull(SettingKey.fromString("invalid")) + } + + @Test + fun `values should return all setting keys`() { + val values = SettingKey.values() + assertEquals(3, values.size) + assertIs<SettingKey.Japanese>(values[0]) + assertIs<SettingKey.Notice>(values[1]) + assertIs<SettingKey.ChNotice>(values[2]) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManagerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManagerTest.kt new file mode 100644 index 0000000..bc716c9 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManagerTest.kt @@ -0,0 +1,159 @@ +package dev.m1sk9.lunaticChat.paper.settings + +import dev.m1sk9.lunaticChat.engine.settings.PlayerChatSettings +import dev.m1sk9.lunaticChat.engine.settings.PlayerSettingsData +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.TestUtils.createTestUUID +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class PlayerSettingsManagerTest { + private fun createManager( + initialData: PlayerSettingsData = PlayerSettingsData(), + ): Triple<PlayerSettingsManager, YamlPlayerSettingsStorage, TestUtils.TestLogger> { + val logger = TestUtils.TestLogger() + val storage = mockk<YamlPlayerSettingsStorage>(relaxed = true) + + every { storage.loadFromDisk() } returns initialData + + val manager = PlayerSettingsManager(storage, logger) + return Triple(manager, storage, logger) + } + + @Test + fun `initialize should load settings from storage`() { + val playerId = createTestUUID(1) + val data = + PlayerSettingsData( + japaneseConversion = mapOf(playerId to false), + directMessageNotification = mapOf(playerId to false), + channelMessageNotification = mapOf(playerId to false), + ) + + val (manager, _, logger) = createManager(initialData = data) + manager.initialize() + + val settings = manager.getSettings(playerId) + assertFalse(settings.japaneseConversionEnabled) + assertFalse(settings.directMessageNotificationEnabled) + assertFalse(settings.channelMessageNotificationEnabled) + assertTrue(logger.infoMessages.any { it.contains("1 players") }) + } + + @Test + fun `getSettings should return defaults for unknown player`() { + val (manager, _, _) = createManager() + manager.initialize() + + val settings = manager.getSettings(createTestUUID(99)) + + assertTrue(settings.japaneseConversionEnabled) + assertTrue(settings.directMessageNotificationEnabled) + assertTrue(settings.channelMessageNotificationEnabled) + } + + @Test + fun `updateSettings should update cache and queue save`() { + val (manager, storage, _) = createManager() + manager.initialize() + + val playerId = createTestUUID(1) + val settings = + PlayerChatSettings( + uuid = playerId, + japaneseConversionEnabled = false, + directMessageNotificationEnabled = false, + channelMessageNotificationEnabled = true, + ) + + manager.updateSettings(settings) + + val retrieved = manager.getSettings(playerId) + assertFalse(retrieved.japaneseConversionEnabled) + assertFalse(retrieved.directMessageNotificationEnabled) + assertTrue(retrieved.channelMessageNotificationEnabled) + + verify(exactly = 1) { storage.queueAsyncSave(any()) } + } + + @Test + fun `updateSettings should overwrite existing settings`() { + val playerId = createTestUUID(1) + val data = + PlayerSettingsData( + japaneseConversion = mapOf(playerId to true), + directMessageNotification = mapOf(playerId to true), + channelMessageNotification = mapOf(playerId to true), + ) + + val (manager, _, _) = createManager(initialData = data) + manager.initialize() + + manager.updateSettings( + PlayerChatSettings( + uuid = playerId, + japaneseConversionEnabled = false, + directMessageNotificationEnabled = false, + channelMessageNotificationEnabled = false, + ), + ) + + val settings = manager.getSettings(playerId) + assertFalse(settings.japaneseConversionEnabled) + assertFalse(settings.directMessageNotificationEnabled) + assertFalse(settings.channelMessageNotificationEnabled) + } + + @Test + fun `getSettings should return correct UUID`() { + val (manager, _, _) = createManager() + manager.initialize() + + val playerId = createTestUUID(1) + val settings = manager.getSettings(playerId) + + assertEquals(playerId, settings.uuid) + } + + @Test + fun `saveToDisk should call storage saveToDisk`() { + val (manager, storage, _) = createManager() + manager.initialize() + + manager.saveToDisk() + + verify(exactly = 1) { storage.saveToDisk(any()) } + } + + @Test + fun `multiple players should have independent settings`() { + val (manager, _, _) = createManager() + manager.initialize() + + val player1 = createTestUUID(1) + val player2 = createTestUUID(2) + + manager.updateSettings( + PlayerChatSettings(uuid = player1, japaneseConversionEnabled = false), + ) + manager.updateSettings( + PlayerChatSettings(uuid = player2, japaneseConversionEnabled = true), + ) + + assertFalse(manager.getSettings(player1).japaneseConversionEnabled) + assertTrue(manager.getSettings(player2).japaneseConversionEnabled) + } + + @Test + fun `initialize with empty data should work`() { + val (manager, _, logger) = createManager(PlayerSettingsData()) + manager.initialize() + + assertTrue(logger.infoMessages.any { it.contains("0 players") }) + } +} |
