diff options
56 files changed, 5170 insertions, 127 deletions
diff --git a/.github/scripts/post-test-results.sh b/.github/scripts/post-test-results.sh deleted file mode 100755 index f8fbdf2..0000000 --- a/.github/scripts/post-test-results.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -set -e - -MARKER="<!-- test-results-comment -->" - -TOTAL_TESTS=0 -PASSED_TESTS=0 -FAILED_TESTS=0 -SKIPPED_TESTS=0 -FAILED_TEST_NAMES="" - -for xml_file in */build/test-results/test/*.xml; do - if [ -f "$xml_file" ]; then - tests=$(sed -n 's/.*tests="\([0-9]*\)".*/\1/p' "$xml_file" | head -n1) - failures=$(sed -n 's/.*failures="\([0-9]*\)".*/\1/p' "$xml_file" | head -n1) - skipped=$(sed -n 's/.*skipped="\([0-9]*\)".*/\1/p' "$xml_file" | head -n1) - - tests=${tests:-0} - failures=${failures:-0} - skipped=${skipped:-0} - - TOTAL_TESTS=$((TOTAL_TESTS + tests)) - FAILED_TESTS=$((FAILED_TESTS + failures)) - SKIPPED_TESTS=$((SKIPPED_TESTS + skipped)) - - if [ "$failures" -gt 0 ]; then - failed_names=$(sed -n 's/.*<testcase name="\([^"]*\)".*<failure.*/\1/p' "$xml_file" || echo "") - if [ -n "$failed_names" ]; then - FAILED_TEST_NAMES="${FAILED_TEST_NAMES}${failed_names}\n" - fi - fi - fi -done - -PASSED_TESTS=$((TOTAL_TESTS - FAILED_TESTS - SKIPPED_TESTS)) - -if [ "$FAILED_TESTS" -gt 0 ]; then - STATUS="❌ Failed" - STATUS_EMOJI="❌" -else - STATUS="✅ Passed" - STATUS_EMOJI="✅" -fi - -COMMENT_BODY="${MARKER} -## ${STATUS_EMOJI} Test Results - -| Status | Count | -|--------|-------| -| ✅ Passed | ${PASSED_TESTS} | -| ❌ Failed | ${FAILED_TESTS} | -| ⏭️ Skipped | ${SKIPPED_TESTS} | -| **Total** | **${TOTAL_TESTS}** | - -**Overall Status:** ${STATUS} -" - -if [ "$FAILED_TESTS" -gt 0 ] && [ -n "$FAILED_TEST_NAMES" ]; then - COMMENT_BODY="${COMMENT_BODY} - -<details> -<summary>❌ Failed Tests</summary> - -$(echo -e "$FAILED_TEST_NAMES" | sed 's/^/- /') - -</details> -" -fi - -COMMENT_BODY="${COMMENT_BODY} - ---- -*Updated at $(date -u '+%Y-%m-%d %H:%M:%S UTC')*" - -echo "$COMMENT_BODY" > /tmp/test-comment.md - -PR_NUMBER="${GITHUB_REF#refs/pull/}" -PR_NUMBER="${PR_NUMBER%/merge}" - -if [ -z "$PR_NUMBER" ] || [ "$PR_NUMBER" = "$GITHUB_REF" ]; then - echo "Not a pull request, skipping comment" - exit 0 -fi - -EXISTING_COMMENT_ID=$(gh api \ - "repos/$GITHUB_REPOSITORY/issues/${PR_NUMBER}/comments" \ - --jq ".[] | select(.body | contains(\"$MARKER\")) | .id" \ - | head -n 1 || echo "") - -if [ -n "$EXISTING_COMMENT_ID" ]; then - echo "Updating existing comment (ID: $EXISTING_COMMENT_ID)" - gh api \ - "repos/$GITHUB_REPOSITORY/issues/comments/${EXISTING_COMMENT_ID}" \ - -X PATCH \ - -f body="$(cat /tmp/test-comment.md)" -else - echo "Creating new comment" - gh pr comment "$PR_NUMBER" --body-file /tmp/test-comment.md -fi - -echo "Test results comment posted successfully" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2f3a9e8..7ab33b6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,16 +29,18 @@ jobs: run: ./gradlew ktlintCheck --no-daemon - name: Run tests - run: ./gradlew test --no-daemon - continue-on-error: true - - - name: Post test results to PR - if: github.event_name == 'pull_request' && always() - env: - GH_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_REF: ${{ github.ref }} - run: .github/scripts/post-test-results.sh + run: ./gradlew test jacocoTestReport --no-daemon + + - name: Upload coverage to Codecov + if: always() + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: | + engine/build/reports/jacoco/test/jacocoTestReport.xml, + platform-paper/build/reports/jacoco/test/jacocoTestReport.xml, + platform-velocity/build/reports/jacoco/test/jacocoTestReport.xml + fail_ci_if_error: false - name: Build with shadowJar run: ./gradlew :platform-paper:shadowJar :platform-velocity:shadowJar --parallel --no-daemon @@ -5,6 +5,7 @@ [](https://github.com/m1sk9/LunaticChat/actions/workflows/dokka.yaml) [](https://github.com/m1sk9/LunaticChat/blob/main/LICENSE)  +[](https://codecov.io/github/m1sk9/LunaticChat) A next-generation chat plugin for Paper, Folia and Velocity. diff --git a/build.gradle.kts b/build.gradle.kts index 36906c9..d2bd265 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import org.gradle.testing.jacoco.tasks.JacocoReport import org.jetbrains.dokka.gradle.DokkaExtension import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -27,6 +28,7 @@ subprojects { apply(plugin = "org.jetbrains.kotlin.jvm") apply(plugin = "org.jlleitschuh.gradle.ktlint") apply(plugin = "org.jetbrains.dokka") + apply(plugin = "jacoco") tasks.withType<KotlinCompile> { compilerOptions { @@ -36,6 +38,15 @@ subprojects { tasks.withType<Test> { useJUnitPlatform() + finalizedBy(tasks.named("jacocoTestReport")) + } + + tasks.withType<JacocoReport> { + dependsOn(tasks.withType<Test>()) + reports { + xml.required.set(true) + html.required.set(false) + } } configure<DokkaExtension> { diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..dcf139a --- /dev/null +++ b/codecov.yml @@ -0,0 +1,15 @@ +coverage: + status: + project: + default: + target: auto + threshold: 1% + patch: + default: + target: 80% +comment: + layout: "condensed_header, condensed_files, condensed_footer" + behavior: default + require_changes: false +ignore: + - "dokka/**" diff --git a/engine/build.gradle.kts b/engine/build.gradle.kts index c178870..0f4e419 100644 --- a/engine/build.gradle.kts +++ b/engine/build.gradle.kts @@ -12,4 +12,6 @@ dependencies { // Adventure API (provided by platform implementations) compileOnly("net.kyori:adventure-api:4.26.1") + + testImplementation("net.kyori:adventure-api:4.26.1") } diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeTest.kt new file mode 100644 index 0000000..5656a14 --- /dev/null +++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/ChatModeTest.kt @@ -0,0 +1,33 @@ +package dev.m1sk9.lunaticChat.engine.chat + +import kotlin.test.Test +import kotlin.test.assertEquals + +class ChatModeTest { + @Test + fun `toggle should switch GLOBAL to CHANNEL`() { + assertEquals(ChatMode.CHANNEL, ChatMode.GLOBAL.toggle()) + } + + @Test + fun `toggle should switch CHANNEL to GLOBAL`() { + assertEquals(ChatMode.GLOBAL, ChatMode.CHANNEL.toggle()) + } + + @Test + fun `DEFAULT should be GLOBAL`() { + assertEquals(ChatMode.GLOBAL, ChatMode.DEFAULT) + } + + @Test + fun `double toggle should return original mode`() { + assertEquals(ChatMode.GLOBAL, ChatMode.GLOBAL.toggle().toggle()) + assertEquals(ChatMode.CHANNEL, ChatMode.CHANNEL.toggle().toggle()) + } + + @Test + fun `enum should have exactly two values`() { + assertEquals(2, ChatMode.entries.size) + assertEquals(setOf(ChatMode.GLOBAL, ChatMode.CHANNEL), ChatMode.entries.toSet()) + } +} diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/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/chat/channel/ChannelTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelTest.kt new file mode 100644 index 0000000..b24e169 --- /dev/null +++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/chat/channel/ChannelTest.kt @@ -0,0 +1,139 @@ +package dev.m1sk9.lunaticChat.engine.chat.channel + +import kotlinx.serialization.json.Json +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class ChannelTest { + private val testOwnerId = UUID.fromString("00000001-0000-0000-0000-000000000000") + private val json = Json { ignoreUnknownKeys = true } + + @Test + fun `valid channel ID should be accepted`() { + val channel = Channel(id = "test-channel", name = "Test", ownerId = testOwnerId) + assertEquals("test-channel", channel.id) + } + + @Test + fun `channel ID with underscores and hyphens should be accepted`() { + val channel = Channel(id = "my_test-channel", name = "Test", ownerId = testOwnerId) + assertEquals("my_test-channel", channel.id) + } + + @Test + fun `channel ID with exactly 3 characters should be accepted`() { + val channel = Channel(id = "abc", name = "Test", ownerId = testOwnerId) + assertEquals("abc", channel.id) + } + + @Test + fun `channel ID with exactly 30 characters should be accepted`() { + val id = "a".repeat(30) + val channel = Channel(id = id, name = "Test", ownerId = testOwnerId) + assertEquals(id, channel.id) + } + + @Test + fun `channel ID with less than 3 characters should be rejected`() { + assertFailsWith<IllegalArgumentException> { + Channel(id = "ab", name = "Test", ownerId = testOwnerId) + } + } + + @Test + fun `channel ID with more than 30 characters should be rejected`() { + assertFailsWith<IllegalArgumentException> { + Channel(id = "a".repeat(31), name = "Test", ownerId = testOwnerId) + } + } + + @Test + fun `channel ID with spaces should be rejected`() { + assertFailsWith<IllegalArgumentException> { + Channel(id = "test channel", name = "Test", ownerId = testOwnerId) + } + } + + @Test + fun `channel ID with special characters should be rejected`() { + assertFailsWith<IllegalArgumentException> { + Channel(id = "test@channel", name = "Test", ownerId = testOwnerId) + } + } + + @Test + fun `empty channel ID should be rejected`() { + assertFailsWith<IllegalArgumentException> { + Channel(id = "", name = "Test", ownerId = testOwnerId) + } + } + + @Test + fun `blank channel name should be rejected`() { + assertFailsWith<IllegalArgumentException> { + Channel(id = "valid-id", name = " ", ownerId = testOwnerId) + } + } + + @Test + fun `empty channel name should be rejected`() { + assertFailsWith<IllegalArgumentException> { + Channel(id = "valid-id", name = "", ownerId = testOwnerId) + } + } + + @Test + fun `default values should be correct`() { + val channel = Channel(id = "test-ch", name = "Test Channel", ownerId = testOwnerId) + assertNull(channel.description) + assertFalse(channel.isPrivate) + assertTrue(channel.bannedPlayers.isEmpty()) + } + + @Test + fun `serialization round-trip should preserve all fields`() { + val bannedPlayer = UUID.fromString("00000002-0000-0000-0000-000000000000") + val original = + Channel( + id = "test-channel", + name = "Test Channel", + description = "A test channel", + isPrivate = true, + ownerId = testOwnerId, + createdAt = 1000L, + bannedPlayers = setOf(bannedPlayer), + ) + + val jsonString = json.encodeToString(Channel.serializer(), original) + val decoded = json.decodeFromString(Channel.serializer(), jsonString) + + assertEquals(original.id, decoded.id) + assertEquals(original.name, decoded.name) + assertEquals(original.description, decoded.description) + assertEquals(original.isPrivate, decoded.isPrivate) + assertEquals(original.ownerId, decoded.ownerId) + assertEquals(original.createdAt, decoded.createdAt) + assertEquals(original.bannedPlayers, decoded.bannedPlayers) + } + + @Test + fun `CHANNEL_ID_PATTERN should match valid patterns`() { + assertTrue("abc".matches(Channel.CHANNEL_ID_PATTERN)) + assertTrue("test-123".matches(Channel.CHANNEL_ID_PATTERN)) + assertTrue("my_channel".matches(Channel.CHANNEL_ID_PATTERN)) + assertTrue("ABC123".matches(Channel.CHANNEL_ID_PATTERN)) + } + + @Test + fun `CHANNEL_ID_PATTERN should reject invalid patterns`() { + assertFalse("ab".matches(Channel.CHANNEL_ID_PATTERN)) + assertFalse("".matches(Channel.CHANNEL_ID_PATTERN)) + assertFalse("test channel".matches(Channel.CHANNEL_ID_PATTERN)) + assertFalse("test@ch".matches(Channel.CHANNEL_ID_PATTERN)) + } +} diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/command/CommandResultTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/command/CommandResultTest.kt new file mode 100644 index 0000000..b80f116 --- /dev/null +++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/command/CommandResultTest.kt @@ -0,0 +1,54 @@ +package dev.m1sk9.lunaticChat.engine.command + +import net.kyori.adventure.text.Component +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs + +class CommandResultTest { + @Test + fun `Success toBrigadierResult should return 1`() { + assertEquals(1, CommandResult.Success.toBrigadierResult()) + } + + @Test + fun `SuccessWithMessage toBrigadierResult should return 1`() { + val result = CommandResult.SuccessWithMessage(Component.text("ok")) + assertEquals(1, result.toBrigadierResult()) + } + + @Test + fun `Failure toBrigadierResult should return 0`() { + val result = CommandResult.Failure(Component.text("error")) + assertEquals(0, result.toBrigadierResult()) + } + + @Test + fun `InvalidUsage toBrigadierResult should return 0`() { + val result = CommandResult.InvalidUsage("/cmd <arg>") + assertEquals(0, result.toBrigadierResult()) + } + + @Test + fun `SuccessWithMessage should preserve message`() { + val message = Component.text("Test message") + val result = CommandResult.SuccessWithMessage(message) + assertIs<CommandResult.SuccessWithMessage>(result) + assertEquals(message, result.message) + } + + @Test + fun `Failure should preserve message`() { + val message = Component.text("Error message") + val result = CommandResult.Failure(message) + assertIs<CommandResult.Failure>(result) + assertEquals(message, result.message) + } + + @Test + fun `InvalidUsage should preserve usage hint`() { + val result = CommandResult.InvalidUsage("/lc setting <key> <on|off>") + assertIs<CommandResult.InvalidUsage>(result) + assertEquals("/lc setting <key> <on|off>", result.usageHint) + } +} diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/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/protocol/PluginMessageCodecTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/PluginMessageCodecTest.kt new file mode 100644 index 0000000..c792aad --- /dev/null +++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/PluginMessageCodecTest.kt @@ -0,0 +1,233 @@ +package dev.m1sk9.lunaticChat.engine.protocol + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertIs + +class PluginMessageCodecTest { + @Test + fun `encode and decode Handshake round-trip`() { + val original = + PluginMessage.Handshake( + pluginVersion = "0.10.0", + protocolMajor = 1, + protocolMinor = 0, + protocolPatch = 0, + ) + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.Handshake>(decoded) + assertEquals(original.pluginVersion, decoded.pluginVersion) + assertEquals(original.protocolMajor, decoded.protocolMajor) + assertEquals(original.protocolMinor, decoded.protocolMinor) + assertEquals(original.protocolPatch, decoded.protocolPatch) + } + + @Test + fun `encode and decode HandshakeResponse compatible round-trip`() { + val original = + PluginMessage.HandshakeResponse( + compatible = true, + velocityVersion = "0.10.0", + error = null, + ) + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.HandshakeResponse>(decoded) + assertEquals(original.compatible, decoded.compatible) + assertEquals(original.velocityVersion, decoded.velocityVersion) + assertEquals(original.error, decoded.error) + } + + @Test + fun `encode and decode HandshakeResponse incompatible round-trip`() { + val original = + PluginMessage.HandshakeResponse( + compatible = false, + velocityVersion = "0.10.0", + error = "Version mismatch", + ) + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.HandshakeResponse>(decoded) + assertEquals(false, decoded.compatible) + assertEquals("Version mismatch", decoded.error) + } + + @Test + fun `encode and decode StatusRequest round-trip`() { + val original = PluginMessage.StatusRequest + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.StatusRequest>(decoded) + } + + @Test + fun `encode and decode StatusResponse round-trip`() { + val original = + PluginMessage.StatusResponse( + velocityVersion = "0.10.0", + protocolVersion = "1.0.0", + online = true, + ) + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.StatusResponse>(decoded) + assertEquals(original.velocityVersion, decoded.velocityVersion) + assertEquals(original.protocolVersion, decoded.protocolVersion) + assertEquals(original.online, decoded.online) + } + + @Test + fun `encode and decode GlobalChatMessage round-trip`() { + val original = + PluginMessage.GlobalChatMessage( + messageId = "test-id-123", + serverName = "lobby", + playerId = "00000001-0000-0000-0000-000000000000", + playerName = "TestPlayer", + message = "Hello, world!", + timestamp = 1000L, + ) + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.GlobalChatMessage>(decoded) + assertEquals(original.messageId, decoded.messageId) + assertEquals(original.serverName, decoded.serverName) + assertEquals(original.playerId, decoded.playerId) + assertEquals(original.playerName, decoded.playerName) + assertEquals(original.message, decoded.message) + assertEquals(original.timestamp, decoded.timestamp) + } + + @Test + fun `decode should throw on unknown sub-channel`() { + val out = java.io.ByteArrayOutputStream() + val dataOut = java.io.DataOutputStream(out) + dataOut.writeUTF("unknown_channel") + dataOut.writeUTF("{}") + + assertFailsWith<IllegalArgumentException> { + PluginMessageCodec.decode(out.toByteArray()) + } + } + + @Test + fun `decode should throw on empty data`() { + assertFailsWith<Exception> { + PluginMessageCodec.decode(byteArrayOf()) + } + } + + @Test + fun `encode and decode GlobalChatMessage with special characters`() { + val original = + PluginMessage.GlobalChatMessage( + messageId = "msg-special", + serverName = "survival", + playerId = "00000002-0000-0000-0000-000000000000", + playerName = "Player_With-Dash", + message = "Hello! こんにちは 🎉 \"quotes\" & <tags>", + timestamp = 2000L, + ) + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.GlobalChatMessage>(decoded) + assertEquals(original.message, decoded.message) + } + + @Test + fun `SubChannel constants should have correct values`() { + assertEquals("handshake", PluginMessageCodec.SubChannel.HANDSHAKE) + assertEquals("handshake_response", PluginMessageCodec.SubChannel.HANDSHAKE_RESPONSE) + assertEquals("status_request", PluginMessageCodec.SubChannel.STATUS_REQUEST) + assertEquals("status_response", PluginMessageCodec.SubChannel.STATUS_RESPONSE) + assertEquals("global_chat", PluginMessageCodec.SubChannel.GLOBAL_CHAT) + } + + @Test + fun `encode should produce non-empty byte array for all message types`() { + val messages = + listOf( + PluginMessage.Handshake("1.0.0", 1, 0, 0), + PluginMessage.HandshakeResponse(true, "1.0.0"), + PluginMessage.StatusRequest, + PluginMessage.StatusResponse("1.0.0", "1.0.0", true), + PluginMessage.GlobalChatMessage("id", "srv", "pid", "name", "msg", 0L), + ) + + messages.forEach { message -> + val encoded = PluginMessageCodec.encode(message) + assert(encoded.isNotEmpty()) { "Encoded ${message::class.simpleName} should not be empty" } + } + } + + @Test + fun `StatusResponse with online false round-trip`() { + val original = + PluginMessage.StatusResponse( + velocityVersion = "0.10.0", + protocolVersion = "1.0.0", + online = false, + ) + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.StatusResponse>(decoded) + assertEquals(false, decoded.online) + } + + @Test + fun `Handshake with various protocol versions round-trip`() { + val original = + PluginMessage.Handshake( + pluginVersion = "2.5.3", + protocolMajor = 99, + protocolMinor = 42, + protocolPatch = 7, + ) + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.Handshake>(decoded) + assertEquals(99, decoded.protocolMajor) + assertEquals(42, decoded.protocolMinor) + assertEquals(7, decoded.protocolPatch) + } + + @Test + fun `GlobalChatMessage with empty message round-trip`() { + val original = + PluginMessage.GlobalChatMessage( + messageId = "msg-empty", + serverName = "lobby", + playerId = "00000003-0000-0000-0000-000000000000", + playerName = "Player", + message = "", + timestamp = 3000L, + ) + + val encoded = PluginMessageCodec.encode(original) + val decoded = PluginMessageCodec.decode(encoded) + + assertIs<PluginMessage.GlobalChatMessage>(decoded) + assertEquals("", decoded.message) + } +} diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/ProtocolVersionTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/ProtocolVersionTest.kt new file mode 100644 index 0000000..7f05039 --- /dev/null +++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/protocol/ProtocolVersionTest.kt @@ -0,0 +1,57 @@ +package dev.m1sk9.lunaticChat.engine.protocol + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ProtocolVersionTest { + @Test + fun `version string should match MAJOR MINOR PATCH format`() { + assertEquals("${ProtocolVersion.MAJOR}.${ProtocolVersion.MINOR}.${ProtocolVersion.PATCH}", ProtocolVersion.version) + } + + @Test + fun `isCompatible with matching major and minor should return true`() { + assertTrue(ProtocolVersion.isCompatible(ProtocolVersion.MAJOR, ProtocolVersion.MINOR)) + } + + @Test + fun `isCompatible with different major should return false`() { + assertFalse(ProtocolVersion.isCompatible(ProtocolVersion.MAJOR + 1, ProtocolVersion.MINOR)) + } + + @Test + fun `isCompatible with different minor should return false`() { + assertFalse(ProtocolVersion.isCompatible(ProtocolVersion.MAJOR, ProtocolVersion.MINOR + 1)) + } + + @Test + fun `isCompatible string with matching version should return true`() { + assertTrue(ProtocolVersion.isCompatible("${ProtocolVersion.MAJOR}.${ProtocolVersion.MINOR}.0")) + } + + @Test + fun `isCompatible string with different patch should return true`() { + assertTrue(ProtocolVersion.isCompatible("${ProtocolVersion.MAJOR}.${ProtocolVersion.MINOR}.99")) + } + + @Test + fun `isCompatible string with different major should return false`() { + assertFalse(ProtocolVersion.isCompatible("${ProtocolVersion.MAJOR + 1}.${ProtocolVersion.MINOR}.0")) + } + + @Test + fun `isCompatible string with malformed version should return false`() { + assertFalse(ProtocolVersion.isCompatible("invalid")) + assertFalse(ProtocolVersion.isCompatible("1.0")) + assertFalse(ProtocolVersion.isCompatible("")) + assertFalse(ProtocolVersion.isCompatible("a.b.c")) + assertFalse(ProtocolVersion.isCompatible("1.0.0.0")) + } + + @Test + fun `isCompatible string with two parts should return false`() { + assertFalse(ProtocolVersion.isCompatible("1.0")) + } +} diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/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())) + } +} diff --git a/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/UUIDSerializerTest.kt b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/UUIDSerializerTest.kt new file mode 100644 index 0000000..599a372 --- /dev/null +++ b/engine/src/test/kotlin/dev/m1sk9/lunaticChat/engine/settings/UUIDSerializerTest.kt @@ -0,0 +1,100 @@ +package dev.m1sk9.lunaticChat.engine.settings + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class UUIDSerializerTest { + private val json = Json + + @Serializable + private data class UUIDHolder( + @Serializable(with = UUIDSerializer::class) + val uuid: UUID, + ) + + @Serializable + private data class UUIDAsStringHolder( + @Serializable(with = UUIDASStringSerializer::class) + val uuid: UUID, + ) + + @Test + fun `UUIDSerializer should serialize UUID to string`() { + val uuid = UUID.fromString("12345678-1234-1234-1234-123456789abc") + val holder = UUIDHolder(uuid) + + val jsonString = json.encodeToString(UUIDHolder.serializer(), holder) + + assert(jsonString.contains("12345678-1234-1234-1234-123456789abc")) + } + + @Test + fun `UUIDSerializer should deserialize string to UUID`() { + val jsonString = """{"uuid":"12345678-1234-1234-1234-123456789abc"}""" + val holder = json.decodeFromString(UUIDHolder.serializer(), jsonString) + + assertEquals(UUID.fromString("12345678-1234-1234-1234-123456789abc"), holder.uuid) + } + + @Test + fun `UUIDSerializer round-trip should preserve UUID`() { + val originalUuid = UUID.randomUUID() + val holder = UUIDHolder(originalUuid) + + val jsonString = json.encodeToString(UUIDHolder.serializer(), holder) + val decoded = json.decodeFromString(UUIDHolder.serializer(), jsonString) + + assertEquals(originalUuid, decoded.uuid) + } + + @Test + fun `UUIDASStringSerializer should serialize UUID to string`() { + val uuid = UUID.fromString("abcdef01-2345-6789-abcd-ef0123456789") + val holder = UUIDAsStringHolder(uuid) + + val jsonString = json.encodeToString(UUIDAsStringHolder.serializer(), holder) + + assert(jsonString.contains("abcdef01-2345-6789-abcd-ef0123456789")) + } + + @Test + fun `UUIDASStringSerializer should deserialize string to UUID`() { + val jsonString = """{"uuid":"abcdef01-2345-6789-abcd-ef0123456789"}""" + val holder = json.decodeFromString(UUIDAsStringHolder.serializer(), jsonString) + + assertEquals(UUID.fromString("abcdef01-2345-6789-abcd-ef0123456789"), holder.uuid) + } + + @Test + fun `UUIDASStringSerializer round-trip should preserve UUID`() { + val originalUuid = UUID.randomUUID() + val holder = UUIDAsStringHolder(originalUuid) + + val jsonString = json.encodeToString(UUIDAsStringHolder.serializer(), holder) + val decoded = json.decodeFromString(UUIDAsStringHolder.serializer(), jsonString) + + assertEquals(originalUuid, decoded.uuid) + } + + @Test + fun `UUIDSerializer should fail on invalid UUID string`() { + val jsonString = """{"uuid":"not-a-valid-uuid"}""" + + assertFailsWith<Exception> { + json.decodeFromString(UUIDHolder.serializer(), jsonString) + } + } + + @Test + fun `UUIDASStringSerializer should fail on invalid UUID string`() { + val jsonString = """{"uuid":"not-a-valid-uuid"}""" + + assertFailsWith<Exception> { + json.decodeFromString(UUIDAsStringHolder.serializer(), jsonString) + } + } +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/StatusCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/StatusCommand.kt index a3dea37..82dcd9f 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/StatusCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/StatusCommand.kt @@ -39,7 +39,7 @@ class StatusCommand( handleResult(context, result) } - private fun execute(ctx: CommandContext): CommandResult { + internal fun execute(ctx: CommandContext): CommandResult { val sender = ctx.requirePlayer() val features = configuration.features diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommand.kt index 90d4e6d..5a6b52f 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommand.kt @@ -68,7 +68,7 @@ class ChannelBanCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, playerName: String, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt index cad2231..fa97a46 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt @@ -81,7 +81,7 @@ class ChannelCreateCommand( ), ) - private fun execute( + internal fun execute( ctx: CommandContext, channelId: String, name: String, diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommand.kt index 8ca3a55..f39d8e8 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommand.kt @@ -72,7 +72,7 @@ class ChannelDeleteCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, channelId: String, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommand.kt index cb040d6..fc32f9b 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommand.kt @@ -63,7 +63,7 @@ class ChannelInfoCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, channelIdArg: String?, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommand.kt index d4ce07a..37c8d7a 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommand.kt @@ -58,7 +58,7 @@ class ChannelInviteCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, playerName: String, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt index 83a40b9..2edb0da 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt @@ -62,7 +62,7 @@ class ChannelJoinCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, channelId: String, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommand.kt index 672cf2d..452c48d 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommand.kt @@ -66,7 +66,7 @@ class ChannelKickCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, playerName: String, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommand.kt index 5e45d27..d763853 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommand.kt @@ -40,7 +40,7 @@ class ChannelLeaveCommand( handleResult(context, result) } - private fun execute(ctx: CommandContext): CommandResult { + internal fun execute(ctx: CommandContext): CommandResult { val sender = ctx.requirePlayer() // Get current channel before leaving diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommand.kt index 1004baf..c0df8bf 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommand.kt @@ -58,7 +58,7 @@ class ChannelListCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, page: Int, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommand.kt index 82caa39..a368abb 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommand.kt @@ -64,7 +64,7 @@ class ChannelModCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, playerName: String, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommand.kt index 576b072..6724052 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommand.kt @@ -64,7 +64,7 @@ class ChannelOwnershipCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, playerName: String, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt index 3b65765..7fb41c6 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt @@ -48,7 +48,7 @@ class ChannelStatusCommand( handleResult(context, result) } - private fun execute(ctx: CommandContext): CommandResult { + internal fun execute(ctx: CommandContext): CommandResult { val sender = ctx.requirePlayer() // Get active channel diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelSwitchCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelSwitchCommand.kt index 13d44d2..6f8cc8f 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelSwitchCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelSwitchCommand.kt @@ -58,7 +58,7 @@ class ChannelSwitchCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, channelId: String, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommand.kt index e7a7295..043ca01 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommand.kt @@ -63,7 +63,7 @@ class ChannelUnbanCommand( }, ) - private fun execute( + internal fun execute( ctx: CommandContext, playerName: String, ): CommandResult { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/chatmode/ChatModeToggleCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/chatmode/ChatModeToggleCommand.kt index cea1814..e447639 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/chatmode/ChatModeToggleCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/chatmode/ChatModeToggleCommand.kt @@ -37,7 +37,7 @@ class ChatModeToggleCommand( handleResult(context, result) } - private fun execute(ctx: CommandContext): CommandResult { + internal fun execute(ctx: CommandContext): CommandResult { val sender = ctx.requirePlayer() val newMode = chatModeManager.toggleChatMode(sender.uniqueId) 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/impl/lc/StatusCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/StatusCommandTest.kt new file mode 100644 index 0000000..95c8b92 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/StatusCommandTest.kt @@ -0,0 +1,125 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.paper.BuildInfo +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.velocity.VelocityConnectionManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import io.mockk.verify +import net.kyori.adventure.text.Component +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class StatusCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + + private fun createDependencies(configuration: LunaticChatConfiguration = TestUtils.createTestConfiguration()): TestDeps { + val plugin = mockk<LunaticChat>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + + every { plugin.velocityConnectionManager } returns null + + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + + val command = StatusCommand(plugin, languageManager, configuration) + return TestDeps(command, ctx, plugin, languageManager, configuration, mockPlayer) + } + + private data class TestDeps( + val command: StatusCommand, + val ctx: CommandContext, + val plugin: LunaticChat, + val languageManager: LanguageManager, + val configuration: LunaticChatConfiguration, + val mockPlayer: org.bukkit.entity.Player, + ) + + @Test + fun `execute should return Success`() { + val deps = createDependencies() + + mockkObject(BuildInfo) + try { + every { BuildInfo.versionWithCommit() } returns "0.10.0-test" + + val result = deps.command.execute(deps.ctx) + + assertIs<CommandResult.Success>(result) + } finally { + unmockkObject(BuildInfo) + } + } + + @Test + fun `execute should send messages to player`() { + val deps = createDependencies() + + mockkObject(BuildInfo) + try { + every { BuildInfo.versionWithCommit() } returns "0.10.0-test" + + deps.command.execute(deps.ctx) + + verify(atLeast = 1) { deps.mockPlayer.sendMessage(any<Component>()) } + } finally { + unmockkObject(BuildInfo) + } + } + + @Test + fun `execute with velocity enabled should check connection state`() { + val velocityConfig = + TestUtils.createTestConfiguration().let { config -> + config.copy( + features = + config.features.copy( + velocityIntegration = config.features.velocityIntegration.copy(enabled = true), + ), + ) + } + val deps = createDependencies(configuration = velocityConfig) + + val velocityManager = mockk<VelocityConnectionManager>(relaxed = true) + every { velocityManager.getState() } returns VelocityConnectionManager.ConnectionState.CONNECTED + every { deps.plugin.velocityConnectionManager } returns velocityManager + + mockkObject(BuildInfo) + try { + every { BuildInfo.versionWithCommit() } returns "0.10.0-test" + + val result = deps.command.execute(deps.ctx) + + assertIs<CommandResult.Success>(result) + } finally { + unmockkObject(BuildInfo) + } + } + + @Test + fun `execute should display feature toggles`() { + val deps = createDependencies() + + mockkObject(BuildInfo) + try { + every { BuildInfo.versionWithCommit() } returns "0.10.0-test" + + deps.command.execute(deps.ctx) + + // Verify multiple sendMessage calls for version, health, features, config, and links + verify(atLeast = 5) { deps.mockPlayer.sendMessage(any<Component>()) } + } finally { + unmockkObject(BuildInfo) + } + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommandTest.kt new file mode 100644 index 0000000..59fc98a --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelBanCommandTest.kt @@ -0,0 +1,137 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.chat.channel.Channel +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerAlreadyBannedException +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import java.util.UUID +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelBanCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + private val targetUUID = UUID.fromString("00000002-0000-0000-0000-000000000000") + private val channelId = "test-ch" + + private val plugin = mockk<LunaticChat>(relaxed = true) + private val channelManager = mockk<ChannelManager>(relaxed = true) + private val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + private val notificationHandler = mockk<ChannelNotificationHandler>(relaxed = true) + private val languageManager = mockk<LanguageManager>(relaxed = true) + + private val command = ChannelBanCommand(plugin, channelManager, membershipManager, notificationHandler, languageManager) + + @BeforeTest + fun setUp() { + mockkStatic(Bukkit::class) + every { languageManager.getMessage(any(), any()) } returns "test message" + } + + @AfterTest + fun tearDown() { + unmockkStatic(Bukkit::class) + } + + private fun createContext(): CommandContext { + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + return ctx + } + + private fun setupOfflinePlayer( + hasPlayedBefore: Boolean = true, + isOnline: Boolean = false, + ): OfflinePlayer { + val offlinePlayer = mockk<OfflinePlayer>(relaxed = true) + every { offlinePlayer.uniqueId } returns targetUUID + every { offlinePlayer.hasPlayedBefore() } returns hasPlayedBefore + every { offlinePlayer.isOnline } returns isOnline + every { offlinePlayer.name } returns "TargetPlayer" + every { Bukkit.getOfflinePlayer(any<String>()) } returns offlinePlayer + return offlinePlayer + } + + @Test + fun `execute should return SuccessWithMessage on ban`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + every { Bukkit.getPlayer(any<String>()) } returns null + + val channel = Channel(id = channelId, name = "Test Channel", ownerId = testUUID, createdAt = 1000L) + every { channelManager.banPlayer(channelId, targetUUID) } returns Result.success(channel) + every { channelManager.getChannel(channelId) } returns Result.success(channel) + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.SuccessWithMessage>(result) + verify { channelManager.banPlayer(channelId, targetUUID) } + } + + @Test + fun `execute should return Failure when no active channel`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns null + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when no permission`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.MEMBER + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when player not found`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer(hasPlayedBefore = false, isOnline = false) + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when already banned`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + every { Bukkit.getPlayer(any<String>()) } returns null + + every { channelManager.banPlayer(channelId, targetUUID) } returns + Result.failure(ChannelPlayerAlreadyBannedException(targetUUID, channelId)) + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommandTest.kt new file mode 100644 index 0000000..9c57eec --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommandTest.kt @@ -0,0 +1,128 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelLimitExceededException +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelCreateCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + + private fun createDependencies(): Triple<CommandContext, ChannelManager, LanguageManager> { + val player = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns player + + val channelManager = mockk<ChannelManager>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + every { languageManager.getMessage(any()) } returns "test message" + + return Triple(ctx, channelManager, languageManager) + } + + @Test + fun `execute with valid ID should return SuccessWithMessage`() { + val (ctx, channelManager, languageManager) = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelCreateCommand(plugin, channelManager, languageManager) + + every { channelManager.createChannel(any()) } returns + Result.success( + TestUtils.createTestChannel(id = "test-ch", name = "Test Channel", ownerId = testUUID), + ) + + val result = command.execute(ctx, "test-ch", "Test Channel", null, false) + + assertIs<CommandResult.SuccessWithMessage>(result) + } + + @Test + fun `execute with private channel should return SuccessWithMessage`() { + val (ctx, channelManager, languageManager) = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelCreateCommand(plugin, channelManager, languageManager) + + every { channelManager.createChannel(any()) } returns + Result.success( + TestUtils.createTestChannel(id = "private-ch", name = "Private Channel", ownerId = testUUID, isPrivate = true), + ) + + val result = command.execute(ctx, "private-ch", "Private Channel", null, true) + + assertIs<CommandResult.SuccessWithMessage>(result) + } + + @Test + fun `execute with invalid ID pattern should return Failure`() { + val (ctx, channelManager, languageManager) = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelCreateCommand(plugin, channelManager, languageManager) + + val result = command.execute(ctx, "ab", "Short ID", null, false) + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute when channel already exists should return Failure`() { + val (ctx, channelManager, languageManager) = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelCreateCommand(plugin, channelManager, languageManager) + + every { channelManager.createChannel(any()) } returns + Result.failure( + IllegalArgumentException("Channel already exists"), + ) + + val result = command.execute(ctx, "existing-ch", "Existing Channel", null, false) + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute when limit exceeded should return Failure`() { + val (ctx, channelManager, languageManager) = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelCreateCommand(plugin, channelManager, languageManager) + + every { channelManager.createChannel(any()) } returns + Result.failure( + ChannelLimitExceededException(10), + ) + + val result = command.execute(ctx, "new-channel", "New Channel", null, false) + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute with description should pass it to channel`() { + val (ctx, channelManager, languageManager) = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelCreateCommand(plugin, channelManager, languageManager) + + every { channelManager.createChannel(any()) } returns + Result.success( + TestUtils.createTestChannel(id = "desc-ch", name = "Desc Channel", description = "test desc", ownerId = testUUID), + ) + + val result = command.execute(ctx, "desc-ch", "Desc Channel", "test desc", false) + + assertIs<CommandResult.SuccessWithMessage>(result) + verify { + channelManager.createChannel( + match { it.description == "test desc" }, + ) + } + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommandTest.kt new file mode 100644 index 0000000..5b76978 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelDeleteCommandTest.kt @@ -0,0 +1,95 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNoOwnerPermissionException +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelDeleteCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + + private fun createDependencies(hasBypass: Boolean = false): Triple<CommandContext, ChannelManager, LanguageManager> { + val player = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + every { player.hasPermission(LunaticChatPermissionNode.ChannelBypass.permissionNode) } returns hasBypass + + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns player + + val channelManager = mockk<ChannelManager>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + every { languageManager.getMessage(any()) } returns "test message" + + return Triple(ctx, channelManager, languageManager) + } + + @Test + fun `execute should return SuccessWithMessage on successful delete`() { + val (ctx, channelManager, languageManager) = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelDeleteCommand(plugin, channelManager, languageManager) + + every { channelManager.deleteChannel("test-ch", testUUID, false) } returns Result.success(Unit) + + val result = command.execute(ctx, "test-ch") + + assertIs<CommandResult.SuccessWithMessage>(result) + } + + @Test + fun `execute should return Failure when channel not found`() { + val (ctx, channelManager, languageManager) = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelDeleteCommand(plugin, channelManager, languageManager) + + every { channelManager.deleteChannel("unknown-ch", testUUID, false) } returns + Result.failure( + ChannelNotFoundException("unknown-ch"), + ) + + val result = command.execute(ctx, "unknown-ch") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when no owner permission`() { + val (ctx, channelManager, languageManager) = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelDeleteCommand(plugin, channelManager, languageManager) + + every { channelManager.deleteChannel("other-ch", testUUID, false) } returns + Result.failure( + ChannelNoOwnerPermissionException(testUUID), + ) + + val result = command.execute(ctx, "other-ch") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute with bypass permission should pass hasBypass true`() { + val (ctx, channelManager, languageManager) = createDependencies(hasBypass = true) + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelDeleteCommand(plugin, channelManager, languageManager) + + every { channelManager.deleteChannel("test-ch", testUUID, true) } returns Result.success(Unit) + + val result = command.execute(ctx, "test-ch") + + assertIs<CommandResult.SuccessWithMessage>(result) + verify { channelManager.deleteChannel("test-ch", testUUID, true) } + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommandTest.kt new file mode 100644 index 0000000..f1cd3a3 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommandTest.kt @@ -0,0 +1,123 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelInfoCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + private val channelId = "test-channel-1" + + private fun createDependencies(): TestDeps { + val plugin = mockk<LunaticChat>(relaxed = true) + val channelManager = mockk<ChannelManager>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + + val command = ChannelInfoCommand(plugin, channelManager, languageManager) + return TestDeps(command, ctx, channelManager, languageManager, mockPlayer) + } + + private data class TestDeps( + val command: ChannelInfoCommand, + val ctx: CommandContext, + val channelManager: ChannelManager, + val languageManager: LanguageManager, + val mockPlayer: org.bukkit.entity.Player, + ) + + private fun mockBukkitOfflinePlayer() { + val offlinePlayer = mockk<OfflinePlayer>(relaxed = true) + every { offlinePlayer.name } returns "Player1" + every { Bukkit.getOfflinePlayer(any<UUID>()) } returns offlinePlayer + } + + @Test + fun `execute with channelId should return Success`() { + val deps = createDependencies() + val channel = TestUtils.createTestChannel(id = channelId, ownerId = testUUID) + val members = + listOf( + TestUtils.createTestChannelMember(channelId = channelId, playerId = testUUID), + ) + + every { deps.channelManager.getChannel(channelId) } returns Result.success(channel) + every { deps.channelManager.getChannelMembers(channelId) } returns Result.success(members) + + mockkStatic(Bukkit::class) + try { + mockBukkitOfflinePlayer() + + val result = deps.command.execute(deps.ctx, channelId) + + assertIs<CommandResult.Success>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } + + @Test + fun `execute with null channelId should use active channel`() { + val deps = createDependencies() + val channel = TestUtils.createTestChannel(id = channelId, ownerId = testUUID) + val members = + listOf( + TestUtils.createTestChannelMember(channelId = channelId, playerId = testUUID), + ) + + every { deps.channelManager.getPlayerChannel(testUUID) } returns channelId + every { deps.channelManager.getChannel(channelId) } returns Result.success(channel) + every { deps.channelManager.getChannelMembers(channelId) } returns Result.success(members) + + mockkStatic(Bukkit::class) + try { + mockBukkitOfflinePlayer() + + val result = deps.command.execute(deps.ctx, null) + + assertIs<CommandResult.Success>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } + + @Test + fun `execute should return Failure when no channel specified and no active channel`() { + val deps = createDependencies() + + every { deps.channelManager.getPlayerChannel(testUUID) } returns null + + val result = deps.command.execute(deps.ctx, null) + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when channel not found`() { + val deps = createDependencies() + + every { deps.channelManager.getChannel("nonexistent") } returns + Result.failure(ChannelNotFoundException("nonexistent")) + + val result = deps.command.execute(deps.ctx, "nonexistent") + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommandTest.kt new file mode 100644 index 0000000..c51f460 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInviteCommandTest.kt @@ -0,0 +1,152 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.bukkit.Bukkit +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelInviteCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + private val targetUUID = UUID.fromString("00000002-0000-0000-0000-000000000000") + private val channelId = "test-channel-1" + + private fun createDependencies(): TestDeps { + val plugin = mockk<LunaticChat>(relaxed = true) + val channelManager = mockk<ChannelManager>(relaxed = true) + val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + + val command = ChannelInviteCommand(plugin, channelManager, membershipManager, languageManager) + return TestDeps(command, ctx, channelManager, membershipManager, languageManager, mockPlayer) + } + + private data class TestDeps( + val command: ChannelInviteCommand, + val ctx: CommandContext, + val channelManager: ChannelManager, + val membershipManager: ChannelMembershipManager, + val languageManager: LanguageManager, + val mockPlayer: org.bukkit.entity.Player, + ) + + @Test + fun `execute should return SuccessWithMessage on invite`() { + val deps = createDependencies() + val targetPlayer = TestUtils.createMockPlayer(uuid = targetUUID, name = "TargetPlayer") + val channel = TestUtils.createTestChannel(id = channelId, ownerId = testUUID) + + every { deps.channelManager.getPlayerChannel(testUUID) } returns channelId + every { deps.membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + every { deps.channelManager.isPlayerBanned(channelId, targetUUID) } returns Result.success(false) + every { deps.membershipManager.joinChannel(targetUUID, channelId, bypassPrivateCheck = true) } returns Result.success(Unit) + every { deps.channelManager.getChannel(channelId) } returns Result.success(channel) + + mockkStatic(Bukkit::class) + try { + every { Bukkit.getPlayer(any<String>()) } returns targetPlayer + + val result = deps.command.execute(deps.ctx, "TargetPlayer") + + assertIs<CommandResult.SuccessWithMessage>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } + + @Test + fun `execute should return Failure when inviting self`() { + val deps = createDependencies() + val selfTarget = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + + every { deps.channelManager.getPlayerChannel(testUUID) } returns channelId + every { deps.membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + + mockkStatic(Bukkit::class) + try { + every { Bukkit.getPlayer(any<String>()) } returns selfTarget + + val result = deps.command.execute(deps.ctx, "Player1") + + assertIs<CommandResult.Failure>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } + + @Test + fun `execute should return Failure when target is banned`() { + val deps = createDependencies() + val targetPlayer = TestUtils.createMockPlayer(uuid = targetUUID, name = "TargetPlayer") + + every { deps.channelManager.getPlayerChannel(testUUID) } returns channelId + every { deps.membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + every { deps.channelManager.isPlayerBanned(channelId, targetUUID) } returns Result.success(true) + + mockkStatic(Bukkit::class) + try { + every { Bukkit.getPlayer(any<String>()) } returns targetPlayer + + val result = deps.command.execute(deps.ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } + + @Test + fun `execute should return Failure when player not found`() { + val deps = createDependencies() + + every { deps.channelManager.getPlayerChannel(testUUID) } returns channelId + every { deps.membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + + mockkStatic(Bukkit::class) + try { + every { Bukkit.getPlayer(any<String>()) } returns null + + val result = deps.command.execute(deps.ctx, "OfflinePlayer") + + assertIs<CommandResult.Failure>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } + + @Test + fun `execute should return Failure when no permission`() { + val deps = createDependencies() + val targetPlayer = TestUtils.createMockPlayer(uuid = targetUUID, name = "TargetPlayer") + + every { deps.channelManager.getPlayerChannel(testUUID) } returns channelId + every { deps.membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.MEMBER + + mockkStatic(Bukkit::class) + try { + every { Bukkit.getPlayer(any<String>()) } returns targetPlayer + + val result = deps.command.execute(deps.ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommandTest.kt new file mode 100644 index 0000000..72f0040 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommandTest.kt @@ -0,0 +1,151 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +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.ChannelPlayerBannedException +import dev.m1sk9.lunaticChat.engine.exception.ChannelPrivateRequiresInvitationException +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelJoinCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + + private data class Dependencies( + val ctx: CommandContext, + val channelManager: ChannelManager, + val membershipManager: ChannelMembershipManager, + val notificationHandler: ChannelNotificationHandler, + val languageManager: LanguageManager, + ) + + private fun createDependencies(): Dependencies { + val player = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns player + + val channelManager = mockk<ChannelManager>(relaxed = true) + val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + val notificationHandler = mockk<ChannelNotificationHandler>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + every { languageManager.getMessage(any()) } returns "test message" + + every { channelManager.getChannel(any()) } returns + Result.success( + TestUtils.createTestChannel(id = "test-ch", name = "Test Channel"), + ) + + return Dependencies(ctx, channelManager, membershipManager, notificationHandler, languageManager) + } + + @Test + fun `execute should return SuccessWithMessage on join`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelJoinCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.membershipManager.joinChannel(testUUID, "test-ch") } returns Result.success(Unit) + + val result = command.execute(deps.ctx, "test-ch") + + assertIs<CommandResult.SuccessWithMessage>(result) + } + + @Test + fun `execute should return Failure when channel not found`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelJoinCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.membershipManager.joinChannel(testUUID, "unknown-ch") } returns + Result.failure( + ChannelNotFoundException("unknown-ch"), + ) + + val result = command.execute(deps.ctx, "unknown-ch") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when already active`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelJoinCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.membershipManager.joinChannel(testUUID, "test-ch") } returns + Result.failure( + ChannelAlreadyActiveException(testUUID, "test-ch"), + ) + + val result = command.execute(deps.ctx, "test-ch") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when already member`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelJoinCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.membershipManager.joinChannel(testUUID, "test-ch") } returns + Result.failure( + ChannelMemberAlreadyException(testUUID, "test-ch"), + ) + + val result = command.execute(deps.ctx, "test-ch") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when banned`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelJoinCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.membershipManager.joinChannel(testUUID, "test-ch") } returns + Result.failure( + ChannelPlayerBannedException(testUUID, "test-ch"), + ) + + val result = command.execute(deps.ctx, "test-ch") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when private without invite`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelJoinCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.membershipManager.joinChannel(testUUID, "private-ch") } returns + Result.failure( + ChannelPrivateRequiresInvitationException(testUUID, "private-ch"), + ) + + val result = command.execute(deps.ctx, "private-ch") + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommandTest.kt new file mode 100644 index 0000000..cab6ef0 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelKickCommandTest.kt @@ -0,0 +1,136 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.chat.channel.Channel +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import java.util.UUID +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelKickCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + private val targetUUID = UUID.fromString("00000002-0000-0000-0000-000000000000") + private val channelId = "test-ch" + + private val plugin = mockk<LunaticChat>(relaxed = true) + private val channelManager = mockk<ChannelManager>(relaxed = true) + private val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + private val notificationHandler = mockk<ChannelNotificationHandler>(relaxed = true) + private val languageManager = mockk<LanguageManager>(relaxed = true) + + private val command = ChannelKickCommand(plugin, channelManager, membershipManager, notificationHandler, languageManager) + + @BeforeTest + fun setUp() { + mockkStatic(Bukkit::class) + every { languageManager.getMessage(any(), any()) } returns "test message" + } + + @AfterTest + fun tearDown() { + unmockkStatic(Bukkit::class) + } + + private fun createContext(): CommandContext { + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + return ctx + } + + private fun setupOfflinePlayer( + hasPlayedBefore: Boolean = true, + isOnline: Boolean = false, + ): OfflinePlayer { + val offlinePlayer = mockk<OfflinePlayer>(relaxed = true) + every { offlinePlayer.uniqueId } returns targetUUID + every { offlinePlayer.hasPlayedBefore() } returns hasPlayedBefore + every { offlinePlayer.isOnline } returns isOnline + every { offlinePlayer.name } returns "TargetPlayer" + every { Bukkit.getOfflinePlayer(any<String>()) } returns offlinePlayer + return offlinePlayer + } + + @Test + fun `execute should return SuccessWithMessage on kick`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + every { Bukkit.getPlayer(any<String>()) } returns null + every { membershipManager.isMember(targetUUID, channelId) } returns Result.success(true) + + val channel = Channel(id = channelId, name = "Test Channel", ownerId = testUUID, createdAt = 1000L) + every { channelManager.removeMember(channelId, targetUUID) } returns Result.success(Unit) + every { channelManager.getPlayerChannel(targetUUID) } returns channelId + every { channelManager.getChannel(channelId) } returns Result.success(channel) + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.SuccessWithMessage>(result) + verify { channelManager.removeMember(channelId, targetUUID) } + } + + @Test + fun `execute should return Failure when no active channel`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns null + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when no permission`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.MEMBER + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when target not member`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + every { Bukkit.getPlayer(any<String>()) } returns null + every { membershipManager.isMember(targetUUID, channelId) } returns Result.success(false) + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when player not found`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer(hasPlayedBefore = false, isOnline = false) + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommandTest.kt new file mode 100644 index 0000000..b97fe99 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelLeaveCommandTest.kt @@ -0,0 +1,125 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotMemberException +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelNotificationHandler +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelLeaveCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + + private data class Dependencies( + val ctx: CommandContext, + val channelManager: ChannelManager, + val membershipManager: ChannelMembershipManager, + val notificationHandler: ChannelNotificationHandler, + val languageManager: LanguageManager, + ) + + private fun createDependencies(): Dependencies { + val player = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns player + + val channelManager = mockk<ChannelManager>(relaxed = true) + val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + val notificationHandler = mockk<ChannelNotificationHandler>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + every { languageManager.getMessage(any()) } returns "test message" + + return Dependencies(ctx, channelManager, membershipManager, notificationHandler, languageManager) + } + + @Test + fun `execute should return SuccessWithMessage on leave`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelLeaveCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.channelManager.getPlayerChannel(testUUID) } returns "ch-1" + every { deps.channelManager.getChannel("ch-1") } returns + Result.success( + TestUtils.createTestChannel(id = "ch-1", name = "Channel One"), + ) + every { deps.membershipManager.leaveChannel(testUUID) } returns Result.success(Unit) + + val result = command.execute(deps.ctx) + + assertIs<CommandResult.SuccessWithMessage>(result) + } + + @Test + fun `execute should return Failure when no active channel`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelLeaveCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.channelManager.getPlayerChannel(testUUID) } returns null + every { deps.membershipManager.leaveChannel(testUUID) } returns + Result.failure( + ChannelNotMemberException(testUUID, ""), + ) + + val result = command.execute(deps.ctx) + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should broadcast leave notification`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelLeaveCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.channelManager.getPlayerChannel(testUUID) } returns "ch-1" + every { deps.channelManager.getChannel("ch-1") } returns + Result.success( + TestUtils.createTestChannel(id = "ch-1", name = "Channel One"), + ) + every { deps.membershipManager.leaveChannel(testUUID) } returns Result.success(Unit) + + command.execute(deps.ctx) + + verify { deps.notificationHandler.broadcastLeave("ch-1", "Player1") } + } + + @Test + fun `execute should show channel name in success message`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = + ChannelLeaveCommand(plugin, deps.channelManager, deps.membershipManager, deps.notificationHandler, deps.languageManager) + + every { deps.channelManager.getPlayerChannel(testUUID) } returns "ch-1" + every { deps.channelManager.getChannel("ch-1") } returns + Result.success( + TestUtils.createTestChannel(id = "ch-1", name = "My Channel"), + ) + every { deps.membershipManager.leaveChannel(testUUID) } returns Result.success(Unit) + + val result = command.execute(deps.ctx) + + assertIs<CommandResult.SuccessWithMessage>(result) + verify { + deps.languageManager.getMessage( + "channel.leave.success", + match { it["channelName"] == "My Channel" }, + ) + } + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommandTest.kt new file mode 100644 index 0000000..5ca6184 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommandTest.kt @@ -0,0 +1,107 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelListCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + + private fun createDependencies(): TestDeps { + val plugin = mockk<LunaticChat>(relaxed = true) + val channelManager = mockk<ChannelManager>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + + val command = ChannelListCommand(plugin, channelManager, languageManager) + return TestDeps(command, ctx, channelManager, languageManager, mockPlayer) + } + + private data class TestDeps( + val command: ChannelListCommand, + val ctx: CommandContext, + val channelManager: ChannelManager, + val languageManager: LanguageManager, + val mockPlayer: org.bukkit.entity.Player, + ) + + @Test + fun `execute should return Success with public channels`() { + val deps = createDependencies() + val channels = + listOf( + TestUtils.createTestChannel(id = "channel-1", name = "Channel 1", ownerId = testUUID), + TestUtils.createTestChannel(id = "channel-2", name = "Channel 2", ownerId = testUUID), + ) + val members = + listOf( + TestUtils.createTestChannelMember(channelId = "channel-1", playerId = testUUID), + ) + + every { deps.channelManager.getPublicChannels() } returns Result.success(channels) + every { deps.channelManager.getChannelMembers(any()) } returns Result.success(members) + + val result = deps.command.execute(deps.ctx, 1) + + assertIs<CommandResult.Success>(result) + } + + @Test + fun `execute should return Success with empty list message`() { + val deps = createDependencies() + + every { deps.channelManager.getPublicChannels() } returns Result.success(emptyList()) + + val result = deps.command.execute(deps.ctx, 1) + + assertIs<CommandResult.Success>(result) + } + + @Test + fun `execute should return Success on page 1 of multiple`() { + val deps = createDependencies() + val channels = + (1..15).map { i -> + TestUtils.createTestChannel( + id = "channel-$i", + name = "Channel $i", + ownerId = testUUID, + ) + } + val members = + listOf( + TestUtils.createTestChannelMember(channelId = "channel-1", playerId = testUUID), + ) + + every { deps.channelManager.getPublicChannels() } returns Result.success(channels) + every { deps.channelManager.getChannelMembers(any()) } returns Result.success(members) + + val result = deps.command.execute(deps.ctx, 1) + + assertIs<CommandResult.Success>(result) + } + + @Test + fun `execute should return Failure on error`() { + val deps = createDependencies() + + every { deps.channelManager.getPublicChannels() } returns Result.failure(ChannelNotFoundException("error")) + + val result = deps.command.execute(deps.ctx, 1) + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommandTest.kt new file mode 100644 index 0000000..da7c7e2 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelModCommandTest.kt @@ -0,0 +1,147 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.chat.channel.Channel +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import java.util.UUID +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelModCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + private val targetUUID = UUID.fromString("00000002-0000-0000-0000-000000000000") + private val channelId = "test-ch" + + private val plugin = mockk<LunaticChat>(relaxed = true) + private val channelManager = mockk<ChannelManager>(relaxed = true) + private val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + private val languageManager = mockk<LanguageManager>(relaxed = true) + + private val command = ChannelModCommand(plugin, channelManager, membershipManager, languageManager) + + @BeforeTest + fun setUp() { + mockkStatic(Bukkit::class) + every { languageManager.getMessage(any(), any()) } returns "test message" + } + + @AfterTest + fun tearDown() { + unmockkStatic(Bukkit::class) + } + + private fun createContext(): CommandContext { + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + return ctx + } + + private fun setupOfflinePlayer( + hasPlayedBefore: Boolean = true, + isOnline: Boolean = false, + ): OfflinePlayer { + val offlinePlayer = mockk<OfflinePlayer>(relaxed = true) + every { offlinePlayer.uniqueId } returns targetUUID + every { offlinePlayer.hasPlayedBefore() } returns hasPlayedBefore + every { offlinePlayer.isOnline } returns isOnline + every { offlinePlayer.name } returns "TargetPlayer" + every { Bukkit.getOfflinePlayer(any<String>()) } returns offlinePlayer + return offlinePlayer + } + + @Test + fun `execute should promote to moderator`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + every { membershipManager.getMemberRoleOrNull(targetUUID, channelId) } returns ChannelRole.MEMBER + + val channel = Channel(id = channelId, name = "Test Channel", ownerId = testUUID, createdAt = 1000L) + every { channelManager.updateMemberRole(channelId, targetUUID, ChannelRole.MODERATOR) } returns Result.success(Unit) + every { channelManager.getChannel(channelId) } returns Result.success(channel) + every { Bukkit.getPlayer(any<String>()) } returns null + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.SuccessWithMessage>(result) + verify { channelManager.updateMemberRole(channelId, targetUUID, ChannelRole.MODERATOR) } + } + + @Test + fun `execute should demote from moderator`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + every { membershipManager.getMemberRoleOrNull(targetUUID, channelId) } returns ChannelRole.MODERATOR + + val channel = Channel(id = channelId, name = "Test Channel", ownerId = testUUID, createdAt = 1000L) + every { channelManager.updateMemberRole(channelId, targetUUID, ChannelRole.MEMBER) } returns Result.success(Unit) + every { channelManager.getChannel(channelId) } returns Result.success(channel) + every { Bukkit.getPlayer(any<String>()) } returns null + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.SuccessWithMessage>(result) + verify { channelManager.updateMemberRole(channelId, targetUUID, ChannelRole.MEMBER) } + } + + @Test + fun `execute should return Failure when modding self`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + + val offlinePlayer = mockk<OfflinePlayer>(relaxed = true) + every { offlinePlayer.uniqueId } returns testUUID + every { offlinePlayer.hasPlayedBefore() } returns true + every { offlinePlayer.isOnline } returns false + every { offlinePlayer.name } returns "Player1" + every { Bukkit.getOfflinePlayer(any<String>()) } returns offlinePlayer + + val result = command.execute(ctx, "Player1") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when not owner`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.MODERATOR + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when target not member`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + every { membershipManager.getMemberRoleOrNull(targetUUID, channelId) } returns null + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommandTest.kt new file mode 100644 index 0000000..9f25264 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelOwnershipCommandTest.kt @@ -0,0 +1,139 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.chat.channel.Channel +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import java.util.UUID +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelOwnershipCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + private val targetUUID = UUID.fromString("00000002-0000-0000-0000-000000000000") + private val channelId = "test-ch" + + private val plugin = mockk<LunaticChat>(relaxed = true) + private val channelManager = mockk<ChannelManager>(relaxed = true) + private val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + private val languageManager = mockk<LanguageManager>(relaxed = true) + + private val command = ChannelOwnershipCommand(plugin, channelManager, membershipManager, languageManager) + + @BeforeTest + fun setUp() { + mockkStatic(Bukkit::class) + every { languageManager.getMessage(any(), any()) } returns "test message" + } + + @AfterTest + fun tearDown() { + unmockkStatic(Bukkit::class) + } + + private fun createContext(): CommandContext { + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + return ctx + } + + private fun setupOfflinePlayer( + hasPlayedBefore: Boolean = true, + isOnline: Boolean = false, + ): OfflinePlayer { + val offlinePlayer = mockk<OfflinePlayer>(relaxed = true) + every { offlinePlayer.uniqueId } returns targetUUID + every { offlinePlayer.hasPlayedBefore() } returns hasPlayedBefore + every { offlinePlayer.isOnline } returns isOnline + every { offlinePlayer.name } returns "TargetPlayer" + every { Bukkit.getOfflinePlayer(any<String>()) } returns offlinePlayer + return offlinePlayer + } + + @Test + fun `execute should transfer ownership successfully`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + every { membershipManager.getMemberRoleOrNull(targetUUID, channelId) } returns ChannelRole.MEMBER + + val updatedChannel = Channel(id = channelId, name = "Test Channel", ownerId = targetUUID, createdAt = 1000L) + every { channelManager.updateChannelOwner(channelId, targetUUID) } returns Result.success(updatedChannel) + every { Bukkit.getPlayer(any<String>()) } returns null + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.SuccessWithMessage>(result) + verify { channelManager.updateChannelOwner(channelId, targetUUID) } + } + + @Test + fun `execute should return Failure when transferring to self`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + + val offlinePlayer = mockk<OfflinePlayer>(relaxed = true) + every { offlinePlayer.uniqueId } returns testUUID + every { offlinePlayer.hasPlayedBefore() } returns true + every { offlinePlayer.isOnline } returns false + every { offlinePlayer.name } returns "Player1" + every { Bukkit.getOfflinePlayer(any<String>()) } returns offlinePlayer + + val result = command.execute(ctx, "Player1") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when target not member`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + every { membershipManager.getMemberRoleOrNull(targetUUID, channelId) } returns null + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when not owner`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.MODERATOR + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when player not found`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer(hasPlayedBefore = false, isOnline = false) + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommandTest.kt new file mode 100644 index 0000000..301160b --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommandTest.kt @@ -0,0 +1,142 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelStatusCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + private val channelId = "test-channel-1" + + private fun createDependencies(): TestDeps { + val plugin = mockk<LunaticChat>(relaxed = true) + val channelManager = mockk<ChannelManager>(relaxed = true) + val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + + val command = ChannelStatusCommand(plugin, channelManager, membershipManager, languageManager) + return TestDeps(command, ctx, channelManager, membershipManager, languageManager, mockPlayer) + } + + private data class TestDeps( + val command: ChannelStatusCommand, + val ctx: CommandContext, + val channelManager: ChannelManager, + val membershipManager: ChannelMembershipManager, + val languageManager: LanguageManager, + val mockPlayer: org.bukkit.entity.Player, + ) + + private fun mockBukkitOfflinePlayer() { + val offlinePlayer = mockk<OfflinePlayer>(relaxed = true) + every { offlinePlayer.name } returns "Player1" + every { Bukkit.getOfflinePlayer(any<UUID>()) } returns offlinePlayer + } + + @Test + fun `execute with active channel should return Success`() { + val deps = createDependencies() + val channel = TestUtils.createTestChannel(id = channelId, ownerId = testUUID) + val members = + listOf( + TestUtils.createTestChannelMember(channelId = channelId, playerId = testUUID), + ) + + every { deps.channelManager.getPlayerChannel(testUUID) } returns channelId + every { deps.channelManager.getChannel(channelId) } returns Result.success(channel) + every { deps.channelManager.getChannelMembers(channelId) } returns Result.success(members) + every { deps.membershipManager.getPlayerChannels(testUUID) } returns Result.success(listOf(channelId)) + + mockkStatic(Bukkit::class) + try { + mockBukkitOfflinePlayer() + + val result = deps.command.execute(deps.ctx) + + assertIs<CommandResult.Success>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } + + @Test + fun `execute without active channel should return Success`() { + val deps = createDependencies() + + every { deps.channelManager.getPlayerChannel(testUUID) } returns null + every { deps.membershipManager.getPlayerChannels(testUUID) } returns Result.success(emptyList()) + + mockkStatic(Bukkit::class) + try { + mockBukkitOfflinePlayer() + + val result = deps.command.execute(deps.ctx) + + assertIs<CommandResult.Success>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } + + @Test + fun `execute with multiple channels should return Success`() { + val deps = createDependencies() + val channelIds = listOf("channel-1", "channel-2", "channel-3") + + every { deps.channelManager.getPlayerChannel(testUUID) } returns "channel-1" + every { deps.channelManager.getChannel("channel-1") } returns + Result.success(TestUtils.createTestChannel(id = "channel-1", name = "Channel 1", ownerId = testUUID)) + every { deps.channelManager.getChannel("channel-2") } returns + Result.success(TestUtils.createTestChannel(id = "channel-2", name = "Channel 2", ownerId = testUUID)) + every { deps.channelManager.getChannel("channel-3") } returns + Result.success(TestUtils.createTestChannel(id = "channel-3", name = "Channel 3", ownerId = testUUID)) + every { deps.channelManager.getChannelMembers(any()) } returns + Result.success( + listOf(TestUtils.createTestChannelMember(playerId = testUUID)), + ) + every { deps.membershipManager.getPlayerChannels(testUUID) } returns Result.success(channelIds) + + mockkStatic(Bukkit::class) + try { + mockBukkitOfflinePlayer() + + val result = deps.command.execute(deps.ctx) + + assertIs<CommandResult.Success>(result) + } finally { + unmockkStatic(Bukkit::class) + } + } + + @Test + fun `execute should return Failure on error`() { + val deps = createDependencies() + + every { deps.channelManager.getPlayerChannel(testUUID) } returns null + every { deps.membershipManager.getPlayerChannels(testUUID) } returns + Result.failure(ChannelNotFoundException("error")) + + val result = deps.command.execute(deps.ctx) + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelSwitchCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelSwitchCommandTest.kt new file mode 100644 index 0000000..eb78956 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelSwitchCommandTest.kt @@ -0,0 +1,108 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelAlreadyActiveException +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException +import dev.m1sk9.lunaticChat.engine.exception.ChannelNotMemberException +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelSwitchCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + + private data class Dependencies( + val ctx: CommandContext, + val channelManager: ChannelManager, + val membershipManager: ChannelMembershipManager, + val languageManager: LanguageManager, + ) + + private fun createDependencies(): Dependencies { + val player = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns player + + val channelManager = mockk<ChannelManager>(relaxed = true) + val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + every { languageManager.getMessage(any()) } returns "test message" + + every { channelManager.getChannel(any()) } returns + Result.success( + TestUtils.createTestChannel(id = "test-ch", name = "Test Channel"), + ) + + return Dependencies(ctx, channelManager, membershipManager, languageManager) + } + + @Test + fun `execute should return SuccessWithMessage on switch`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelSwitchCommand(plugin, deps.channelManager, deps.membershipManager, deps.languageManager) + + every { deps.membershipManager.switchChannel(testUUID, "test-ch") } returns Result.success(Unit) + + val result = command.execute(deps.ctx, "test-ch") + + assertIs<CommandResult.SuccessWithMessage>(result) + } + + @Test + fun `execute should return Failure when channel not found`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelSwitchCommand(plugin, deps.channelManager, deps.membershipManager, deps.languageManager) + + every { deps.membershipManager.switchChannel(testUUID, "unknown-ch") } returns + Result.failure( + ChannelNotFoundException("unknown-ch"), + ) + + val result = command.execute(deps.ctx, "unknown-ch") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when already active`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelSwitchCommand(plugin, deps.channelManager, deps.membershipManager, deps.languageManager) + + every { deps.membershipManager.switchChannel(testUUID, "test-ch") } returns + Result.failure( + ChannelAlreadyActiveException(testUUID, "test-ch"), + ) + + val result = command.execute(deps.ctx, "test-ch") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when not member`() { + val deps = createDependencies() + val plugin = mockk<LunaticChat>(relaxed = true) + val command = ChannelSwitchCommand(plugin, deps.channelManager, deps.membershipManager, deps.languageManager) + + every { deps.membershipManager.switchChannel(testUUID, "other-ch") } returns + Result.failure( + ChannelNotMemberException(testUUID, "other-ch"), + ) + + val result = command.execute(deps.ctx, "other-ch") + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommandTest.kt new file mode 100644 index 0000000..16dd383 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelUnbanCommandTest.kt @@ -0,0 +1,121 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel + +import dev.m1sk9.lunaticChat.engine.chat.channel.Channel +import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.engine.exception.ChannelPlayerNotBannedException +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager +import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelMembershipManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import java.util.UUID +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertIs + +class ChannelUnbanCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + private val targetUUID = UUID.fromString("00000002-0000-0000-0000-000000000000") + private val channelId = "test-ch" + + private val plugin = mockk<LunaticChat>(relaxed = true) + private val channelManager = mockk<ChannelManager>(relaxed = true) + private val membershipManager = mockk<ChannelMembershipManager>(relaxed = true) + private val languageManager = mockk<LanguageManager>(relaxed = true) + + private val command = ChannelUnbanCommand(plugin, channelManager, membershipManager, languageManager) + + @BeforeTest + fun setUp() { + mockkStatic(Bukkit::class) + every { languageManager.getMessage(any(), any()) } returns "test message" + } + + @AfterTest + fun tearDown() { + unmockkStatic(Bukkit::class) + } + + private fun createContext(): CommandContext { + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + return ctx + } + + private fun setupOfflinePlayer( + hasPlayedBefore: Boolean = true, + isOnline: Boolean = false, + ): OfflinePlayer { + val offlinePlayer = mockk<OfflinePlayer>(relaxed = true) + every { offlinePlayer.uniqueId } returns targetUUID + every { offlinePlayer.hasPlayedBefore() } returns hasPlayedBefore + every { offlinePlayer.isOnline } returns isOnline + every { offlinePlayer.name } returns "TargetPlayer" + every { Bukkit.getOfflinePlayer(any<String>()) } returns offlinePlayer + return offlinePlayer + } + + @Test + fun `execute should return SuccessWithMessage on unban`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + + val channel = Channel(id = channelId, name = "Test Channel", ownerId = testUUID, createdAt = 1000L) + every { channelManager.unbanPlayer(channelId, targetUUID) } returns Result.success(channel) + every { channelManager.getChannel(channelId) } returns Result.success(channel) + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.SuccessWithMessage>(result) + verify { channelManager.unbanPlayer(channelId, targetUUID) } + } + + @Test + fun `execute should return Failure when no active channel`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns null + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when no permission`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.MEMBER + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } + + @Test + fun `execute should return Failure when player not banned`() { + val ctx = createContext() + every { channelManager.getPlayerChannel(testUUID) } returns channelId + every { membershipManager.getMemberRoleOrNull(testUUID, channelId) } returns ChannelRole.OWNER + setupOfflinePlayer() + + every { channelManager.unbanPlayer(channelId, targetUUID) } returns + Result.failure(ChannelPlayerNotBannedException(targetUUID, channelId)) + + val result = command.execute(ctx, "TargetPlayer") + + assertIs<CommandResult.Failure>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/chatmode/ChatModeToggleCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/chatmode/ChatModeToggleCommandTest.kt new file mode 100644 index 0000000..d43c867 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/chatmode/ChatModeToggleCommandTest.kt @@ -0,0 +1,88 @@ +package dev.m1sk9.lunaticChat.paper.command.impl.lc.chatmode + +import dev.m1sk9.lunaticChat.engine.chat.ChatMode +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.chat.ChatModeManager +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import net.kyori.adventure.text.Component +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class ChatModeToggleCommandTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + + private fun createDependencies(): TestDeps { + val plugin = mockk<LunaticChat>(relaxed = true) + val chatModeManager = mockk<ChatModeManager>(relaxed = true) + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + + val mockPlayer = TestUtils.createMockPlayer(uuid = testUUID, name = "Player1") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns mockPlayer + + val command = ChatModeToggleCommand(plugin, chatModeManager, languageManager) + return TestDeps(command, ctx, chatModeManager, languageManager, mockPlayer) + } + + private data class TestDeps( + val command: ChatModeToggleCommand, + val ctx: CommandContext, + val chatModeManager: ChatModeManager, + val languageManager: LanguageManager, + val mockPlayer: org.bukkit.entity.Player, + ) + + @Test + fun `execute should toggle to CHANNEL mode`() { + val deps = createDependencies() + + every { deps.chatModeManager.toggleChatMode(testUUID) } returns ChatMode.CHANNEL + + val result = deps.command.execute(deps.ctx) + + assertIs<CommandResult.Success>(result) + verify { deps.chatModeManager.toggleChatMode(testUUID) } + } + + @Test + fun `execute should toggle to GLOBAL mode`() { + val deps = createDependencies() + + every { deps.chatModeManager.toggleChatMode(testUUID) } returns ChatMode.GLOBAL + + val result = deps.command.execute(deps.ctx) + + assertIs<CommandResult.Success>(result) + verify { deps.chatModeManager.toggleChatMode(testUUID) } + } + + @Test + fun `execute should send message to player`() { + val deps = createDependencies() + + every { deps.chatModeManager.toggleChatMode(testUUID) } returns ChatMode.CHANNEL + + deps.command.execute(deps.ctx) + + verify { deps.mockPlayer.sendMessage(any<Component>()) } + } + + @Test + fun `execute should return Success`() { + val deps = createDependencies() + + every { deps.chatModeManager.toggleChatMode(testUUID) } returns ChatMode.GLOBAL + + val result = deps.command.execute(deps.ctx) + + assertIs<CommandResult.Success>(result) + } +} diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/setting/SettingHandlerRegistryTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/setting/SettingHandlerRegistryTest.kt new file mode 100644 index 0000000..52927a8 --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/setting/SettingHandlerRegistryTest.kt @@ -0,0 +1,101 @@ +package dev.m1sk9.lunaticChat.paper.command.setting + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class SettingHandlerRegistryTest { + private fun createMockHandler(settingKey: SettingKey): SettingHandler = + object : SettingHandler { + override val key: SettingKey = settingKey + + override fun execute( + ctx: CommandContext, + enable: Boolean, + ): CommandResult = CommandResult.Success + + override fun showStatus(ctx: CommandContext): CommandResult = CommandResult.Success + } + + @Test + fun `register should make handler retrievable`() { + val registry = SettingHandlerRegistry() + val handler = createMockHandler(SettingKey.Japanese) + + registry.register(handler) + + assertEquals(handler, registry.getHandler(SettingKey.Japanese)) + } + + @Test + fun `registerAll should register multiple handlers`() { + val registry = SettingHandlerRegistry() + val handler1 = createMockHandler(SettingKey.Japanese) + val handler2 = createMockHandler(SettingKey.Notice) + val handler3 = createMockHandler(SettingKey.ChNotice) + + registry.registerAll(handler1, handler2, handler3) + + assertEquals(handler1, registry.getHandler(SettingKey.Japanese)) + assertEquals(handler2, registry.getHandler(SettingKey.Notice)) + assertEquals(handler3, registry.getHandler(SettingKey.ChNotice)) + } + + @Test + fun `getHandler should return null for unregistered key`() { + val registry = SettingHandlerRegistry() + + assertNull(registry.getHandler(SettingKey.Japanese)) + } + + @Test + fun `getAvailableKeys should return registered keys`() { + val registry = SettingHandlerRegistry() + registry.register(createMockHandler(SettingKey.Japanese)) + registry.register(createMockHandler(SettingKey.Notice)) + + val keys = registry.getAvailableKeys() + + assertEquals(2, keys.size) + assertTrue(keys.contains("japanese")) + assertTrue(keys.contains("notice")) + } + + @Test + fun `getAvailableKeys should return empty list for empty registry`() { + val registry = SettingHandlerRegistry() + assertTrue(registry.getAvailableKeys().isEmpty()) + } + + @Test + fun `register should overwrite handler with same key`() { + val registry = SettingHandlerRegistry() + val handler1 = createMockHandler(SettingKey.Japanese) + val handler2 = createMockHandler(SettingKey.Japanese) + + registry.register(handler1) + registry.register(handler2) + + assertEquals(handler2, registry.getHandler(SettingKey.Japanese)) + assertEquals(1, registry.getAvailableKeys().size) + } + + @Test + fun `register should return registry for method chaining`() { + val registry = SettingHandlerRegistry() + val result = registry.register(createMockHandler(SettingKey.Japanese)) + + assertEquals(registry, result) + } + + @Test + fun `registerAll should return registry for method chaining`() { + val registry = SettingHandlerRegistry() + val result = registry.registerAll(createMockHandler(SettingKey.Japanese)) + + assertEquals(registry, result) + } +} 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/command/setting/handler/SettingHandlerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/setting/handler/SettingHandlerTest.kt new file mode 100644 index 0000000..1967e1c --- /dev/null +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/setting/handler/SettingHandlerTest.kt @@ -0,0 +1,191 @@ +package dev.m1sk9.lunaticChat.paper.command.setting.handler + +import dev.m1sk9.lunaticChat.engine.command.CommandResult +import dev.m1sk9.lunaticChat.paper.TestUtils +import dev.m1sk9.lunaticChat.paper.command.core.CommandContext +import dev.m1sk9.lunaticChat.paper.command.setting.SettingKey +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import net.kyori.adventure.text.Component +import java.util.UUID +import kotlin.test.Test +import kotlin.test.assertIs + +class SettingHandlerTest { + private val testUUID = UUID.fromString("00000001-0000-0000-0000-000000000000") + + private fun createDependencies(): Triple<CommandContext, PlayerSettingsManager, LanguageManager> { + val player = TestUtils.createMockPlayer(uuid = testUUID, name = "TestPlayer") + val ctx = mockk<CommandContext>(relaxed = true) + every { ctx.requirePlayer() } returns player + + val settingsManager = mockk<PlayerSettingsManager>(relaxed = true) + every { settingsManager.getSettings(testUUID) } returns + TestUtils.createTestPlayerSettings(uuid = testUUID) + + val languageManager = mockk<LanguageManager>(relaxed = true) + every { languageManager.getMessage(any(), any()) } returns "test message" + every { languageManager.getToggleText(any()) } returns "ON" + + return Triple(ctx, settingsManager, languageManager) + } + + // --- JapaneseConversionSettingHandler --- + + @Test + fun `JapaneseConversionSettingHandler key should be Japanese`() { + val (_, settingsManager, languageManager) = createDependencies() + val handler = JapaneseConversionSettingHandler(settingsManager, languageManager) + assertIs<SettingKey.Japanese>(handler.key) + } + + @Test + fun `JapaneseConversionSettingHandler execute enable should return Success`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = JapaneseConversionSettingHandler(settingsManager, languageManager) + + val result = handler.execute(ctx, true) + + assertIs<CommandResult.Success>(result) + verify { settingsManager.updateSettings(match { it.japaneseConversionEnabled }) } + } + + @Test + fun `JapaneseConversionSettingHandler execute disable should return Success`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = JapaneseConversionSettingHandler(settingsManager, languageManager) + + val result = handler.execute(ctx, false) + + assertIs<CommandResult.Success>(result) + verify { settingsManager.updateSettings(match { !it.japaneseConversionEnabled }) } + } + + @Test + fun `JapaneseConversionSettingHandler showStatus should return Success`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = JapaneseConversionSettingHandler(settingsManager, languageManager) + + val result = handler.showStatus(ctx) + + assertIs<CommandResult.Success>(result) + } + + @Test + fun `JapaneseConversionSettingHandler execute should send message to player`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = JapaneseConversionSettingHandler(settingsManager, languageManager) + + handler.execute(ctx, true) + + val player = ctx.requirePlayer() + verify { player.sendMessage(any<Component>()) } + } + + // --- DirectMessageNoticeSettingHandler --- + + @Test + fun `DirectMessageNoticeSettingHandler key should be Notice`() { + val (_, settingsManager, languageManager) = createDependencies() + val handler = DirectMessageNoticeSettingHandler(settingsManager, languageManager) + assertIs<SettingKey.Notice>(handler.key) + } + + @Test + fun `DirectMessageNoticeSettingHandler execute enable should return Success`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = DirectMessageNoticeSettingHandler(settingsManager, languageManager) + + val result = handler.execute(ctx, true) + + assertIs<CommandResult.Success>(result) + verify { settingsManager.updateSettings(match { it.directMessageNotificationEnabled }) } + } + + @Test + fun `DirectMessageNoticeSettingHandler execute disable should return Success`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = DirectMessageNoticeSettingHandler(settingsManager, languageManager) + + val result = handler.execute(ctx, false) + + assertIs<CommandResult.Success>(result) + verify { settingsManager.updateSettings(match { !it.directMessageNotificationEnabled }) } + } + + @Test + fun `DirectMessageNoticeSettingHandler showStatus should return Success`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = DirectMessageNoticeSettingHandler(settingsManager, languageManager) + + val result = handler.showStatus(ctx) + + assertIs<CommandResult.Success>(result) + } + + @Test + fun `DirectMessageNoticeSettingHandler execute should send message to player`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = DirectMessageNoticeSettingHandler(settingsManager, languageManager) + + handler.execute(ctx, true) + + val player = ctx.requirePlayer() + verify { player.sendMessage(any<Component>()) } + } + + // --- ChannelMessageNoticeSettingHandler --- + + @Test + fun `ChannelMessageNoticeSettingHandler key should be ChNotice`() { + val (_, settingsManager, languageManager) = createDependencies() + val handler = ChannelMessageNoticeSettingHandler(settingsManager, languageManager) + assertIs<SettingKey.ChNotice>(handler.key) + } + + @Test + fun `ChannelMessageNoticeSettingHandler execute enable should return Success`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = ChannelMessageNoticeSettingHandler(settingsManager, languageManager) + + val result = handler.execute(ctx, true) + + assertIs<CommandResult.Success>(result) + verify { settingsManager.updateSettings(match { it.channelMessageNotificationEnabled }) } + } + + @Test + fun `ChannelMessageNoticeSettingHandler execute disable should return Success`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = ChannelMessageNoticeSettingHandler(settingsManager, languageManager) + + val result = handler.execute(ctx, false) + + assertIs<CommandResult.Success>(result) + verify { settingsManager.updateSettings(match { !it.channelMessageNotificationEnabled }) } + } + + @Test + fun `ChannelMessageNoticeSettingHandler showStatus should return Success`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = ChannelMessageNoticeSettingHandler(settingsManager, languageManager) + + val result = handler.showStatus(ctx) + + assertIs<CommandResult.Success>(result) + } + + @Test + fun `ChannelMessageNoticeSettingHandler execute should send message to player`() { + val (ctx, settingsManager, languageManager) = createDependencies() + val handler = ChannelMessageNoticeSettingHandler(settingsManager, languageManager) + + handler.execute(ctx, true) + + val player = ctx.requirePlayer() + verify { player.sendMessage(any<Component>()) } + } +} 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") }) + } +} diff --git a/platform-velocity/src/test/kotlin/dev/m1sk9/lunaticChat/velocity/messaging/CrossServerChatRelayTest.kt b/platform-velocity/src/test/kotlin/dev/m1sk9/lunaticChat/velocity/messaging/CrossServerChatRelayTest.kt new file mode 100644 index 0000000..50b0db2 --- /dev/null +++ b/platform-velocity/src/test/kotlin/dev/m1sk9/lunaticChat/velocity/messaging/CrossServerChatRelayTest.kt @@ -0,0 +1,136 @@ +package dev.m1sk9.lunaticChat.velocity.messaging + +import com.velocitypowered.api.proxy.ProxyServer +import com.velocitypowered.api.proxy.messages.ChannelIdentifier +import com.velocitypowered.api.proxy.server.RegisteredServer +import dev.m1sk9.lunaticChat.engine.protocol.PluginMessage +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.slf4j.Logger +import kotlin.test.Test + +class CrossServerChatRelayTest { + private fun createRelay(): Triple<CrossServerChatRelay, ProxyServer, Logger> { + val server = mockk<ProxyServer>(relaxed = true) + val logger = mockk<Logger>(relaxed = true) + val relay = CrossServerChatRelay(server, logger) + return Triple(relay, server, logger) + } + + private fun createRegisteredServer(name: String): RegisteredServer { + val server = mockk<RegisteredServer>(relaxed = true) + val serverInfo = mockk<com.velocitypowered.api.proxy.server.ServerInfo>(relaxed = true) + every { serverInfo.name } returns name + every { server.serverInfo } returns serverInfo + return server + } + + private fun createTestMessage( + messageId: String = "msg-1", + serverName: String = "lobby", + ): PluginMessage.GlobalChatMessage = + PluginMessage.GlobalChatMessage( + messageId = messageId, + serverName = serverName, + playerId = "00000001-0000-0000-0000-000000000000", + playerName = "TestPlayer", + message = "Hello!", + timestamp = 1000L, + ) + + @Test + fun `relayGlobalMessage should exclude source server`() { + val (relay, proxyServer, _) = createRelay() + val sourceServer = createRegisteredServer("lobby") + val targetServer = createRegisteredServer("survival") + + every { proxyServer.allServers } returns listOf(sourceServer, targetServer) + + relay.relayGlobalMessage(createTestMessage(), sourceServer) + + verify(exactly = 1) { targetServer.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + verify(exactly = 0) { sourceServer.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + } + + @Test + fun `relayGlobalMessage should relay to multiple servers`() { + val (relay, proxyServer, _) = createRelay() + val sourceServer = createRegisteredServer("lobby") + val target1 = createRegisteredServer("survival") + val target2 = createRegisteredServer("creative") + + every { proxyServer.allServers } returns listOf(sourceServer, target1, target2) + + relay.relayGlobalMessage(createTestMessage(), sourceServer) + + verify(exactly = 1) { target1.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + verify(exactly = 1) { target2.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + } + + @Test + fun `relayGlobalMessage with only source server should relay to zero`() { + val (relay, proxyServer, logger) = createRelay() + val sourceServer = createRegisteredServer("lobby") + + every { proxyServer.allServers } returns listOf(sourceServer) + + relay.relayGlobalMessage(createTestMessage(), sourceServer) + + verify(exactly = 0) { sourceServer.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + verify { logger.info(match { it.contains("0 servers") }) } + } + + @Test + fun `relayGlobalMessage should log with messageId and playerName`() { + val (relay, proxyServer, logger) = createRelay() + val sourceServer = createRegisteredServer("lobby") + val targetServer = createRegisteredServer("survival") + + every { proxyServer.allServers } returns listOf(sourceServer, targetServer) + + val message = createTestMessage(messageId = "test-msg-123") + relay.relayGlobalMessage(message, sourceServer) + + verify { + logger.info( + match { msg -> + msg.contains("test-msg-123") && msg.contains("TestPlayer") + }, + ) + } + } + + @Test + fun `relayGlobalMessage should log error on exception`() { + val (relay, proxyServer, logger) = createRelay() + val sourceServer = createRegisteredServer("lobby") + val targetServer = createRegisteredServer("survival") + + every { proxyServer.allServers } returns listOf(sourceServer, targetServer) + every { targetServer.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } throws + RuntimeException("connection failed") + + relay.relayGlobalMessage(createTestMessage(), sourceServer) + + verify { logger.error(match { it.contains("Failed to relay") }, any<Exception>()) } + } + + @Test + fun `relayGlobalMessage should send encoded message data`() { + val (relay, proxyServer, _) = createRelay() + val sourceServer = createRegisteredServer("lobby") + val targetServer = createRegisteredServer("survival") + + every { proxyServer.allServers } returns listOf(sourceServer, targetServer) + + relay.relayGlobalMessage(createTestMessage(), sourceServer) + + verify { + targetServer.sendPluginMessage( + any<ChannelIdentifier>(), + match<ByteArray> { it.isNotEmpty() }, + ) + } + } +} diff --git a/platform-velocity/src/test/kotlin/dev/m1sk9/lunaticChat/velocity/messaging/PluginMessageHandlerTest.kt b/platform-velocity/src/test/kotlin/dev/m1sk9/lunaticChat/velocity/messaging/PluginMessageHandlerTest.kt new file mode 100644 index 0000000..876eec6 --- /dev/null +++ b/platform-velocity/src/test/kotlin/dev/m1sk9/lunaticChat/velocity/messaging/PluginMessageHandlerTest.kt @@ -0,0 +1,211 @@ +package dev.m1sk9.lunaticChat.velocity.messaging + +import com.velocitypowered.api.event.connection.PluginMessageEvent +import com.velocitypowered.api.proxy.ProxyServer +import com.velocitypowered.api.proxy.ServerConnection +import com.velocitypowered.api.proxy.messages.ChannelIdentifier +import com.velocitypowered.api.proxy.messages.ChannelMessageSink +import com.velocitypowered.api.proxy.messages.ChannelMessageSource +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier +import com.velocitypowered.api.proxy.server.RegisteredServer +import com.velocitypowered.api.proxy.server.ServerInfo +import dev.m1sk9.lunaticChat.engine.protocol.PluginMessage +import dev.m1sk9.lunaticChat.engine.protocol.PluginMessageCodec +import dev.m1sk9.lunaticChat.engine.protocol.ProtocolVersion +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.slf4j.Logger +import kotlin.test.Test + +class PluginMessageHandlerTest { + private val channel = MinecraftChannelIdentifier.create("lunaticchat", "main") + + private fun createHandler(pluginVersion: String = "0.10.0"): Triple<PluginMessageHandler, ProxyServer, CrossServerChatRelay> { + val plugin = Any() + val server = mockk<ProxyServer>(relaxed = true) + val logger = mockk<Logger>(relaxed = true) + val relay = mockk<CrossServerChatRelay>(relaxed = true) + + val handler = PluginMessageHandler(plugin, server, logger, pluginVersion, relay) + return Triple(handler, server, relay) + } + + private fun createServerConnection(serverName: String = "lobby"): ServerConnection { + val connection = mockk<ServerConnection>(relaxed = true) + val serverInfo = mockk<ServerInfo>(relaxed = true) + val registeredServer = mockk<RegisteredServer>(relaxed = true) + every { serverInfo.name } returns serverName + every { connection.serverInfo } returns serverInfo + every { connection.server } returns registeredServer + return connection + } + + private fun createPluginMessageEvent( + source: ChannelMessageSource, + target: ChannelMessageSink, + identifier: ChannelIdentifier, + data: ByteArray, + ): PluginMessageEvent = PluginMessageEvent(source, target, identifier, data) + + @Test + fun `onPluginMessage should handle successful handshake`() { + val (handler, _, _) = createHandler(pluginVersion = "0.10.0") + val connection = createServerConnection() + + val handshake = + PluginMessage.Handshake( + pluginVersion = "0.10.0", + protocolMajor = ProtocolVersion.MAJOR, + protocolMinor = ProtocolVersion.MINOR, + protocolPatch = ProtocolVersion.PATCH, + ) + val data = PluginMessageCodec.encode(handshake) + val event = createPluginMessageEvent(connection, mockk(relaxed = true), channel, data) + + handler.onPluginMessage(event) + + verify { connection.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + } + + @Test + fun `onPluginMessage should reject version mismatch handshake`() { + val (handler, _, _) = createHandler(pluginVersion = "0.10.0") + val connection = createServerConnection() + + val handshake = + PluginMessage.Handshake( + pluginVersion = "0.9.0", + protocolMajor = ProtocolVersion.MAJOR, + protocolMinor = ProtocolVersion.MINOR, + protocolPatch = ProtocolVersion.PATCH, + ) + val data = PluginMessageCodec.encode(handshake) + val event = createPluginMessageEvent(connection, mockk(relaxed = true), channel, data) + + handler.onPluginMessage(event) + + verify { connection.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + } + + @Test + fun `onPluginMessage should reject protocol mismatch handshake`() { + val (handler, _, _) = createHandler(pluginVersion = "0.10.0") + val connection = createServerConnection() + + val handshake = + PluginMessage.Handshake( + pluginVersion = "0.10.0", + protocolMajor = ProtocolVersion.MAJOR + 1, + protocolMinor = ProtocolVersion.MINOR, + protocolPatch = ProtocolVersion.PATCH, + ) + val data = PluginMessageCodec.encode(handshake) + val event = createPluginMessageEvent(connection, mockk(relaxed = true), channel, data) + + handler.onPluginMessage(event) + + verify { connection.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + } + + @Test + fun `onPluginMessage should handle status request`() { + val (handler, _, _) = createHandler() + val connection = createServerConnection() + + val data = PluginMessageCodec.encode(PluginMessage.StatusRequest) + val event = createPluginMessageEvent(connection, mockk(relaxed = true), channel, data) + + handler.onPluginMessage(event) + + verify { connection.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + } + + @Test + fun `onPluginMessage should relay global chat message`() { + val (handler, _, relay) = createHandler() + val connection = createServerConnection() + + val chatMessage = + PluginMessage.GlobalChatMessage( + messageId = "msg-1", + serverName = "lobby", + playerId = "00000001-0000-0000-0000-000000000000", + playerName = "TestPlayer", + message = "Hello!", + timestamp = 1000L, + ) + val data = PluginMessageCodec.encode(chatMessage) + val event = createPluginMessageEvent(connection, mockk(relaxed = true), channel, data) + + handler.onPluginMessage(event) + + verify { relay.relayGlobalMessage(any<PluginMessage.GlobalChatMessage>(), any<RegisteredServer>()) } + } + + @Test + fun `onPluginMessage should ignore messages from wrong channel`() { + val (handler, _, relay) = createHandler() + val connection = createServerConnection() + + val wrongChannel = MinecraftChannelIdentifier.create("other", "channel") + val data = PluginMessageCodec.encode(PluginMessage.StatusRequest) + val event = createPluginMessageEvent(connection, mockk(relaxed = true), wrongChannel, data) + + handler.onPluginMessage(event) + + verify(exactly = 0) { relay.relayGlobalMessage(any<PluginMessage.GlobalChatMessage>(), any<RegisteredServer>()) } + } + + @Test + fun `onPluginMessage should ignore messages from non-server source`() { + val (handler, _, relay) = createHandler() + val source = mockk<ChannelMessageSource>(relaxed = true) + + val data = PluginMessageCodec.encode(PluginMessage.StatusRequest) + val event = createPluginMessageEvent(source, mockk(relaxed = true), channel, data) + + handler.onPluginMessage(event) + + verify(exactly = 0) { relay.relayGlobalMessage(any<PluginMessage.GlobalChatMessage>(), any<RegisteredServer>()) } + } + + @Test + fun `initialize should register channel and listener`() { + val (handler, server, _) = createHandler() + + handler.initialize() + + verify { server.channelRegistrar.register(any<ChannelIdentifier>()) } + verify { server.eventManager.register(any(), handler) } + } + + @Test + fun `shutdown should unregister listener`() { + val (handler, server, _) = createHandler() + + handler.shutdown() + + verify { server.eventManager.unregisterListener(any(), handler) } + } + + @Test + fun `handshake with matching patch difference should succeed`() { + val (handler, _, _) = createHandler(pluginVersion = "0.10.0") + val connection = createServerConnection() + + val handshake = + PluginMessage.Handshake( + pluginVersion = "0.10.0", + protocolMajor = ProtocolVersion.MAJOR, + protocolMinor = ProtocolVersion.MINOR, + protocolPatch = ProtocolVersion.PATCH + 5, + ) + val data = PluginMessageCodec.encode(handshake) + val event = createPluginMessageEvent(connection, mockk(relaxed = true), channel, data) + + handler.onPluginMessage(event) + + verify { connection.sendPluginMessage(any<ChannelIdentifier>(), any<ByteArray>()) } + } +} |
