소스 검색

Unit test for actions

ryoii 2 년 전
부모
커밋
300ff05e50

+ 25 - 1
mirai-api-http/src/test/kotlin/framework/TestMahApplication.kt

@@ -70,7 +70,7 @@ class MahApplicationTestBuilder(private val builder: ApplicationTestBuilder): Cl
             contentConverter = KotlinxWebsocketSerializationConverter(Json)
         }
         install(ContentNegotiation) {
-            json()
+            json(json = buildPolyJson())
         }
     }}
 
@@ -84,6 +84,30 @@ class MahApplicationTestBuilder(private val builder: ApplicationTestBuilder): Cl
     }.body<T>()
 }
 
+@KtorDsl
+fun testHttpApplication(
+    verifyKey: String = "verifyKey",
+    enableVerify: Boolean = false,
+    singleMode: Boolean = true,
+    debug: Boolean = false,
+    block: suspend MahApplicationTestBuilder.() -> Unit
+) = testMahApplication(verifyKey, enableVerify, singleMode, debug) {
+    installHttpAdapter()
+    block.invoke(this)
+}
+
+@KtorDsl
+fun testWebsocketApplication(
+    verifyKey: String = "verifyKey",
+    enableVerify: Boolean = false,
+    singleMode: Boolean = true,
+    debug: Boolean = false,
+    block: suspend MahApplicationTestBuilder.() -> Unit
+) = testMahApplication(verifyKey, enableVerify, singleMode, debug) {
+    installWsAdapter()
+    block.invoke(this)
+}
+
 @KtorDsl
 fun testMahApplication(
     verifyKey: String = "verifyKey",

+ 26 - 2
mirai-api-http/src/test/kotlin/framework/extend.kt

@@ -9,6 +9,8 @@
 
 package framework
 
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.mock.MockBot
 import net.mamoe.mirai.mock.MockBotFactory
 import org.junit.jupiter.api.extension.BeforeAllCallback
 import org.junit.jupiter.api.extension.ExtensionContext
@@ -23,11 +25,33 @@ class SetupMockBot : BeforeAllCallback {
             .id(ID)
             .create()
 
-        bot.addFriend(FRIEND_ID, "friend")
+        bot.addFriend(BEST_FRIEND_ID, "best_friend")
+        bot.addFriend(WORST_FRIEND_ID, "worst_friend")
+        bot.addGroup(BEST_GROUP_ID, "best_group").apply {
+            addMember(BEST_MEMBER_ID, "best_member")
+            addMember(GOOD_MEMBER_ID, "good_member")
+        }
+        bot.addGroup(WORST_GROUP_ID, "worst_group").apply {
+            addMember(WORST_MEMBER_ID, "worst_member")
+            addMember(BAD_MEMBER_ID, "bad_member")
+        }
     }
 
     companion object {
         const val ID = 1L
-        const val FRIEND_ID = 11L
+        const val BEST_FRIEND_ID = 11L
+        const val WORST_FRIEND_ID = 99L
+
+        const val BEST_GROUP_ID = 111L
+        const val BEST_MEMBER_ID = 11111L
+        const val GOOD_MEMBER_ID = 11122L
+
+        const val WORST_GROUP_ID = 999L
+        const val WORST_MEMBER_ID = 99999L
+        const val BAD_MEMBER_ID = 99988L
+
+        fun instance(): MockBot {
+            return Bot.getInstance(ID) as MockBot
+        }
     }
 }

+ 34 - 0
mirai-api-http/src/test/kotlin/framework/json.kt

@@ -0,0 +1,34 @@
+package framework
+
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.SerializersModuleBuilder
+import kotlinx.serialization.serializer
+import net.mamoe.mirai.api.http.adapter.internal.dto.BotEventDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.EventDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.MessagePacketDTO
+import kotlin.reflect.KClass
+
+
+fun buildPolyJson() = Json {
+    serializersModule = SerializersModule {
+        polymorphicSealedClass(EventDTO::class, MessagePacketDTO::class)
+        polymorphicSealedClass(EventDTO::class, BotEventDTO::class)
+    }
+}
+
+/**
+ * 从 sealed class 里注册到多态序列化
+ */
+@OptIn(InternalSerializationApi::class)
+@Suppress("UNCHECKED_CAST")
+private fun <B : Any, S : B> SerializersModuleBuilder.polymorphicSealedClass(
+    baseClass: KClass<B>,
+    sealedClass: KClass<S>
+) {
+    sealedClass.sealedSubclasses.forEach {
+        val c = it as KClass<S>
+        polymorphic(baseClass, c, c.serializer())
+    }
+}

+ 57 - 0
mirai-api-http/src/test/kotlin/integration/action/AboutActionTest.kt

@@ -0,0 +1,57 @@
+package integration.action
+
+import framework.SetupMockBot
+import framework.testMahApplication
+import integration.withSession
+import io.ktor.client.call.*
+import io.ktor.client.request.*
+import net.mamoe.mirai.api.http.adapter.common.StateCode
+import net.mamoe.mirai.api.http.adapter.internal.consts.Paths
+import net.mamoe.mirai.api.http.adapter.internal.dto.*
+import net.mamoe.mirai.api.http.adapter.internal.serializer.jsonElementParseOrNull
+import org.junit.jupiter.api.extension.ExtendWith
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+
+@ExtendWith(SetupMockBot::class)
+class AboutActionTest {
+
+    private val pathsVerify = Paths.httpPath("verify")
+    private val pathsBind = Paths.httpPath("bind")
+
+    @Test
+    fun testOnGetSessionInfo() = testMahApplication(
+        enableVerify = true,
+        singleMode = false,
+    ) {
+        installHttpAdapter()
+
+        val verifyRet = postJsonData<VerifyRetDTO>(pathsVerify, VerifyDTO("verifyKey")).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertNotNull(it.session)
+        }
+
+        // bind
+        postJsonData<StateCode>(pathsBind, BindDTO(SetupMockBot.ID).withSession(verifyRet.session)).also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+
+        client.get(Paths.sessionInfo){ parameter("sessionKey", verifyRet.session) }.body<ElementResult>().also {
+            val session = it.data.jsonElementParseOrNull<SessionDTO>()
+            assertNotNull(session)
+            assertEquals(verifyRet.session, session.sessionKey)
+        }
+    }
+
+    @Test
+    fun testOnGetBotList() = testMahApplication {
+        installHttpAdapter()
+
+        client.get(Paths.botList).body<LongListRestfulResult>().also {
+            assertEquals(1, it.data.size)
+            assertEquals(SetupMockBot.ID, it.data[0])
+        }
+    }
+}

+ 133 - 0
mirai-api-http/src/test/kotlin/integration/action/AnnouncementActionTest.kt

@@ -0,0 +1,133 @@
+package integration.action
+
+import framework.ExtendWith
+import framework.SetupMockBot
+import framework.testMahApplication
+import io.ktor.client.call.*
+import io.ktor.client.request.*
+import kotlinx.coroutines.flow.first
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.api.http.adapter.common.StateCode
+import net.mamoe.mirai.api.http.adapter.internal.consts.Paths
+import net.mamoe.mirai.api.http.adapter.internal.dto.AnnouncementDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.ElementResult
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.AnnouncementDeleteDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.AnnouncementList
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.PublishAnnouncementDTO
+import net.mamoe.mirai.api.http.adapter.internal.serializer.jsonElementParseOrNull
+import net.mamoe.mirai.console.util.cast
+import net.mamoe.mirai.contact.announcement.AnnouncementParameters
+import net.mamoe.mirai.contact.getMemberOrFail
+import net.mamoe.mirai.mock.MockBot
+import net.mamoe.mirai.mock.contact.announcement.MockOnlineAnnouncement
+import net.mamoe.mirai.utils.MiraiInternalApi
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.MethodOrderer
+import org.junit.jupiter.api.Order
+import org.junit.jupiter.api.TestMethodOrder
+import kotlin.test.*
+
+@ExtendWith(SetupMockBot::class)
+@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
+class AnnouncementActionTest {
+
+    @Test
+    @Order(1)
+    fun testOnListAnnouncement() = testMahApplication {
+        installHttpAdapter()
+
+        client.get(Paths.httpPath(Paths.announcementList)) {
+            parameter("id", SetupMockBot.BEST_GROUP_ID)
+        }.body<AnnouncementList>().also {
+            assertEquals(1, it.data.size)
+
+            val announcement = it.data[0]
+            assertEquals("announcement content", announcement.content)
+            assertEquals(SetupMockBot.BEST_MEMBER_ID, announcement.senderId)
+            assertEquals(SetupMockBot.BEST_GROUP_ID, announcement.group.id)
+        }
+    }
+
+    @Test
+    @Order(2)
+    fun testOnPublishAnnouncement() = testMahApplication {
+        installHttpAdapter()
+
+        postJsonData<ElementResult>(
+            Paths.httpPath(Paths.announcementPublish), PublishAnnouncementDTO(
+                SetupMockBot.BEST_GROUP_ID,
+                "new announcement content",
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+            val announcement = it.data.jsonElementParseOrNull<AnnouncementDTO>()
+
+            assertNotNull(announcement)
+            assertEquals("new announcement content", announcement.content)
+            assertEquals(SetupMockBot.ID, announcement.senderId)
+            assertEquals(SetupMockBot.BEST_GROUP_ID, announcement.group.id)
+
+            Bot.getInstance(SetupMockBot.ID).groups[SetupMockBot.BEST_GROUP_ID]!!.announcements.get(announcement.fid)
+                .also { groupAnnouncement ->
+                    assertNotNull(groupAnnouncement)
+                    assertEquals("new announcement content", groupAnnouncement.content)
+                    assertEquals(SetupMockBot.ID, announcement.senderId)
+                    assertEquals(SetupMockBot.BEST_GROUP_ID, groupAnnouncement.group.id)
+                }
+        }
+    }
+
+    @Test
+    @Order(3)
+    fun testOnDeleteAnnouncement() = testMahApplication {
+        installHttpAdapter()
+
+        val fid = Bot.getInstance(SetupMockBot.ID).groups[SetupMockBot.BEST_GROUP_ID]!!.announcements.asFlow().first().fid
+
+        postJsonData<StateCode>(
+            Paths.httpPath(Paths.announcementDelete),
+            AnnouncementDeleteDTO(
+                SetupMockBot.BEST_GROUP_ID,
+                fid,
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+
+            Bot.getInstance(SetupMockBot.ID).groups[SetupMockBot.BEST_GROUP_ID]!!.announcements.get(fid)
+                .also(::assertNull)
+        }
+
+        postJsonData<StateCode>(
+            Paths.httpPath(Paths.announcementDelete),
+            AnnouncementDeleteDTO(
+                SetupMockBot.BEST_GROUP_ID,
+                fid,
+            )
+        ).also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        @OptIn(MiraiInternalApi::class)
+        @BeforeAll
+        fun setUpAnnouncement() {
+            val bot: MockBot = Bot.getInstance(SetupMockBot.ID).cast()
+            bot.groups[SetupMockBot.BEST_GROUP_ID]!!.announcements
+                .mockPublish(
+                    MockOnlineAnnouncement(
+                        "announcement content",
+                        AnnouncementParameters.DEFAULT,
+                        SetupMockBot.BEST_MEMBER_ID,
+                        "mock announcement fid",
+                        false,
+                        0,
+                        1,
+                    ),
+                    bot.getGroupOrFail(SetupMockBot.BEST_GROUP_ID).getMemberOrFail(SetupMockBot.BEST_MEMBER_ID),
+                    false
+                )
+        }
+    }
+}

+ 168 - 0
mirai-api-http/src/test/kotlin/integration/action/EventActionTest.kt

@@ -0,0 +1,168 @@
+package integration.action
+
+import framework.ExtendWith
+import framework.SetupMockBot
+import framework.testHttpApplication
+import net.mamoe.mirai.api.http.adapter.common.StateCode
+import net.mamoe.mirai.api.http.adapter.internal.consts.Paths
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.EventRespDTO
+import net.mamoe.mirai.event.broadcast
+import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
+import net.mamoe.mirai.event.events.MemberJoinRequestEvent
+import net.mamoe.mirai.utils.MiraiInternalApi
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+
+@ExtendWith(SetupMockBot::class)
+class EventActionTest {
+
+    @Test
+    fun testNewFriend() = testHttpApplication {
+        val newFriendId = 11133L
+        val bot = SetupMockBot.instance()
+
+        bot.broadcastNewFriendRequestEvent(
+            newFriendId,
+            "nickname",
+            SetupMockBot.BEST_GROUP_ID,
+            "hello"
+        ).let { event ->
+            assertNull(bot.getFriend(newFriendId))
+            postJsonData<StateCode>(Paths.httpPath(Paths.newFriend), EventRespDTO(
+                event.eventId,
+                event.fromId,
+                event.fromGroupId,
+                1,
+                ""
+            )).also {
+                assertEquals(StateCode.Success.code, it.code)
+                assertNull(bot.getFriend(newFriendId))
+            }
+        }
+
+        // accept
+        bot.broadcastNewFriendRequestEvent(
+            newFriendId,
+            "nickname",
+            SetupMockBot.BEST_GROUP_ID,
+            "hello"
+        ).let { event ->
+            assertNull(bot.getFriend(newFriendId))
+            postJsonData<StateCode>(Paths.httpPath(Paths.newFriend), EventRespDTO(
+                event.eventId,
+                event.fromId,
+                event.fromGroupId,
+                0,
+                ""
+            )).also {
+                assertEquals(StateCode.Success.code, it.code)
+                assertNotNull(bot.getFriend(newFriendId))
+            }
+        }
+    }
+
+    @Test
+    @OptIn(MiraiInternalApi::class)
+    fun testMemberJoin() = testHttpApplication {
+        val newMemberId = 11133L
+        val bot = SetupMockBot.instance()
+
+        MemberJoinRequestEvent(
+            bot,
+            Random.nextLong(),
+            "hello",
+            newMemberId,
+            SetupMockBot.BEST_GROUP_ID,
+            "Best Group",
+            "new member",
+            invitorId = SetupMockBot.BEST_MEMBER_ID,
+        ).broadcast().let { event ->
+            assertNull(bot.getGroupOrFail(SetupMockBot.BEST_GROUP_ID)[newMemberId])
+            postJsonData<StateCode>(Paths.httpPath(Paths.memberJoin), EventRespDTO(
+                event.eventId,
+                event.fromId,
+                event.groupId,
+                1,
+                "reject"
+            )).also {
+                assertEquals(StateCode.Success.code, it.code)
+                assertNull(bot.getGroupOrFail(SetupMockBot.BEST_GROUP_ID)[newMemberId])
+            }
+        }
+
+        MemberJoinRequestEvent(
+            bot,
+            Random.nextLong(),
+            "hello",
+            newMemberId,
+            SetupMockBot.BEST_GROUP_ID,
+            "Best Group",
+            "new member",
+            invitorId = SetupMockBot.BEST_MEMBER_ID,
+        ).broadcast().let { event ->
+            assertNull(bot.getGroupOrFail(SetupMockBot.BEST_GROUP_ID)[newMemberId])
+            postJsonData<StateCode>(Paths.httpPath(Paths.memberJoin), EventRespDTO(
+                event.eventId,
+                event.fromId,
+                event.groupId,
+                0,
+                "accept"
+            )).also {
+                assertEquals(StateCode.Success.code, it.code)
+                assertNotNull(bot.getGroupOrFail(SetupMockBot.BEST_GROUP_ID)[newMemberId])
+            }
+        }
+    }
+
+    @Test
+    @OptIn(MiraiInternalApi::class)
+    fun testBotInvited() = testHttpApplication {
+        val newGroupId = 222L
+        val bot = SetupMockBot.instance()
+
+        BotInvitedJoinGroupRequestEvent(
+            bot,
+            Random.nextLong(),
+            SetupMockBot.BEST_FRIEND_ID,
+            newGroupId,
+            "new group",
+            "best friend"
+        ).broadcast().also { event ->
+            assertNull(bot.getGroup(newGroupId))
+            postJsonData<StateCode>(Paths.httpPath(Paths.memberJoin), EventRespDTO(
+                event.eventId,
+                event.invitorId,
+                event.groupId,
+                1,
+                "reject"
+            )).also {
+                assertEquals(StateCode.Success.code, it.code)
+                assertNull(bot.getGroup(newGroupId))
+            }
+        }
+
+        BotInvitedJoinGroupRequestEvent(
+            bot,
+            Random.nextLong(),
+            SetupMockBot.BEST_FRIEND_ID,
+            newGroupId,
+            "new group",
+            "best friend"
+        ).broadcast().also { event ->
+            assertNull(bot.getGroup(newGroupId))
+            postJsonData<StateCode>(Paths.httpPath(Paths.botInvited), EventRespDTO(
+                event.eventId,
+                event.invitorId,
+                event.groupId,
+                0,
+                "accept"
+            )).also {
+                assertEquals(StateCode.Success.code, it.code)
+                assertNotNull(bot.getGroup(newGroupId))
+            }
+        }
+    }
+}

+ 301 - 0
mirai-api-http/src/test/kotlin/integration/action/FileActionTest.kt

@@ -0,0 +1,301 @@
+package integration.action
+
+import framework.ExtendWith
+import framework.SetupMockBot
+import framework.testHttpApplication
+import io.ktor.client.call.*
+import io.ktor.client.request.*
+import io.ktor.client.request.forms.*
+import io.ktor.http.*
+import io.ktor.http.content.*
+import io.ktor.utils.io.streams.*
+import kotlinx.coroutines.runBlocking
+import net.mamoe.mirai.api.http.adapter.common.StateCode
+import net.mamoe.mirai.api.http.adapter.internal.consts.Paths
+import net.mamoe.mirai.api.http.adapter.internal.dto.ElementResult
+import net.mamoe.mirai.api.http.adapter.internal.dto.RemoteFileDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.*
+import net.mamoe.mirai.api.http.adapter.internal.serializer.jsonElementParseOrNull
+import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.MethodOrderer
+import org.junit.jupiter.api.Order
+import org.junit.jupiter.api.TestMethodOrder
+import kotlin.test.*
+
+@ExtendWith(SetupMockBot::class)
+@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
+class FileActionTest {
+
+    @Test
+    @Order(1)
+    fun testListFile() = testHttpApplication {
+        client.get(Paths.httpPath(Paths.fileList)) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("path", "/")
+        }.body<RemoteFileList>().also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertEquals(2, it.data.size)
+
+            val (file, folder) = if (it.data[0].isFile) {
+                it.data[0] to it.data[1]
+            } else {
+                it.data[1] to it.data[0]
+            }
+
+            assertTrue(folder.isDirectory)
+            assertEquals("/tmpFolder", folder.path)
+            assertEquals("tmpFolder", folder.name)
+            assertNotNull(folder.parent)
+            assertEquals("/", folder.parent.path)
+            assertNull(folder.parent.parent)
+
+            assertTrue(file.isFile)
+            assertEquals("/test.txt", file.path)
+            assertEquals("test.txt", file.name)
+            assertNotNull(file.parent)
+            assertEquals("/", file.parent.path)
+            assertNull(file.parent.parent)
+        }
+    }
+
+    @Test
+    @Order(2)
+    fun testFileInfo() = testHttpApplication {
+        client.get(Paths.httpPath(Paths.fileInfo)) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("path", "/test.txt")
+            parameter("withDownloadInfo", true)
+        }.body<ElementResult>().also {
+            assertEquals(StateCode.Success.code, it.code)
+
+            val data = it.data.jsonElementParseOrNull<RemoteFileDTO>()
+            assertNotNull(data)
+            assertEquals("/test.txt", data.path)
+            assertEquals("test.txt", data.name)
+            assertNotNull(data.parent)
+            assertEquals("/", data.parent.path)
+            assertNull(data.parent.parent)
+
+            val download = data.downloadInfo
+            assertNotNull(download)
+            assertNotNull(download.url)
+            assertNotNull(download.md5)
+            assertNotNull(download.sha1)
+        }
+
+        client.get(Paths.httpPath(Paths.fileInfo)) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("path", "/test.txt")
+            parameter("withDownloadInfo", false)
+        }.body<ElementResult>().also {
+            assertEquals(StateCode.Success.code, it.code)
+
+            val data = it.data.jsonElementParseOrNull<RemoteFileDTO>()
+            assertNotNull(data)
+            assertNull(data.downloadInfo)
+        }
+
+        client.get(Paths.httpPath(Paths.fileInfo)) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("path", "/tmpFolder")
+            parameter("withDownloadInfo", true)
+        }.body<StateCode>().also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+    }
+
+    @Test
+    @Order(3)
+    fun testFileMkdir() = testHttpApplication {
+        client.get(Paths.httpPath(Paths.fileList)) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("path", "/mkdir")
+        }.body<StateCode>().also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+
+        postJsonData<ElementResult>(
+            Paths.httpPath(Paths.fileMkdir), MkDirDTO(
+                path = "/", target = SetupMockBot.BEST_GROUP_ID, directoryName = "mkdir"
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+
+            val data = it.data.jsonElementParseOrNull<RemoteFileDTO>()
+            assertNotNull(data)
+            assertTrue(data.isDirectory)
+            assertEquals("/mkdir", data.path)
+            assertEquals("mkdir", data.name)
+            assertNotNull(data.parent)
+            assertEquals("/", data.parent.path)
+        }
+
+        client.get(Paths.httpPath(Paths.fileList)) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("path", "/mkdir")
+        }.body<RemoteFileList>().also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertTrue(it.data.isEmpty())
+        }
+    }
+
+    @Test
+    @Order(4)
+    fun testUploadFile() = testHttpApplication {
+        client.get(Paths.httpPath(Paths.fileInfo)) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("path", "/upload.txt")
+        }.body<StateCode>().also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+
+        client.submitFormWithBinaryData(Paths.httpPath(Paths.uploadFile), formData {
+            append("path", "/")
+            append("type", "group")
+            append("target", SetupMockBot.BEST_GROUP_ID)
+            append("file", "content", Headers.build {
+                append(HttpHeaders.ContentDisposition, "filename=upload.txt")
+            })
+        }).body<ElementResult>().also {
+            assertEquals(StateCode.Success.code, it.code)
+
+            val data = it.data.jsonElementParseOrNull<RemoteFileDTO>()
+            assertNotNull(data)
+            assertTrue(data.isFile)
+            assertEquals("/upload.txt", data.path)
+            assertEquals("upload.txt", data.name)
+        }
+
+        client.get(Paths.httpPath(Paths.fileInfo)) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("path", "/upload.txt")
+        }.body<ElementResult>().also {
+            assertEquals(StateCode.Success.code, it.code)
+
+            val data = it.data.jsonElementParseOrNull<RemoteFileDTO>()
+            assertNotNull(data)
+        }
+    }
+
+    @Test
+    @Order(5)
+    fun testFileMove() = testHttpApplication {
+        client.submitFormWithBinaryData(Paths.httpPath(Paths.uploadFile), formData {
+            append("path", "/")
+            append("type", "group")
+            append("target", SetupMockBot.BEST_GROUP_ID)
+            append("file", "content", Headers.build {
+                append(HttpHeaders.ContentDisposition, "filename=move.txt")
+            })
+        }).body<ElementResult>().also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+
+        postJsonData<StateCode>(
+            Paths.httpPath(Paths.fileMove), MoveFileDTO(
+                path = "/move.txt", target = SetupMockBot.BEST_GROUP_ID, moveToPath = "/noExist"
+            )
+        ).also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+
+        postJsonData<StateCode>(
+            Paths.httpPath(Paths.fileMove), MoveFileDTO(
+                path = "/move.txt", target = SetupMockBot.BEST_GROUP_ID, moveToPath = "/tmpFolder"
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+
+        client.get(Paths.httpPath(Paths.fileInfo)) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("path", "/tmpFolder/move.txt")
+        }.body<ElementResult>().also {
+            assertEquals(StateCode.Success.code, it.code)
+
+            val data = it.data.jsonElementParseOrNull<RemoteFileDTO>()
+            assertNotNull(data)
+        }
+    }
+
+    @Test
+    @Order(6)
+    fun testFileRename() = testHttpApplication {
+        client.submitFormWithBinaryData(Paths.httpPath(Paths.uploadFile), formData {
+            append("path", "/")
+            append("type", "group")
+            append("target", SetupMockBot.BEST_GROUP_ID)
+            append("file", "content", Headers.build {
+                append(HttpHeaders.ContentDisposition, "filename=rename.txt")
+            })
+        }).body<ElementResult>().also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+
+        postJsonData<StateCode>(
+            Paths.httpPath(Paths.fileRename), RenameFileDTO(
+                path = "/noExist.txt", group = SetupMockBot.BEST_GROUP_ID, renameTo = "newName.txt"
+            )
+        ).also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+
+        postJsonData<StateCode>(
+            Paths.httpPath(Paths.fileRename), RenameFileDTO(
+                path = "/rename.txt", group = SetupMockBot.BEST_GROUP_ID, renameTo = "newName.txt"
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+    }
+
+    @Test
+    @Order(7)
+    fun testFileDelete() = testHttpApplication {
+        client.submitFormWithBinaryData(Paths.httpPath(Paths.uploadFile), formData {
+            append("path", "/")
+            append("type", "group")
+            append("target", SetupMockBot.BEST_GROUP_ID)
+            append("file", "content", Headers.build {
+                append(HttpHeaders.ContentDisposition, "filename=delete.txt")
+            })
+        }).body<ElementResult>().also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+
+        postJsonData<StateCode>(
+            Paths.httpPath(Paths.fileDelete), FileTargetDTO(
+                path = "/noExist.txt",
+                group = SetupMockBot.BEST_GROUP_ID,
+            )
+        ).also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+
+        postJsonData<StateCode>(
+            Paths.httpPath(Paths.fileDelete), FileTargetDTO(
+                path = "/delete.txt",
+                group = SetupMockBot.BEST_GROUP_ID,
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+    }
+
+    companion object {
+
+        @JvmStatic
+        @BeforeAll
+        fun setupGroupFile(): Unit = runBlocking {
+            SetupMockBot.instance().getGroupOrFail(SetupMockBot.BEST_GROUP_ID).files.apply {
+                "content".byteInputStream().toExternalResource().use {
+                    uploadNewFile("/test.txt", it)
+                }
+
+
+                root.createFolder("tmpFolder")
+            }
+        }
+    }
+}

+ 54 - 0
mirai-api-http/src/test/kotlin/integration/action/FriendActionTest.kt

@@ -0,0 +1,54 @@
+package integration.action
+
+import framework.ExtendWith
+import framework.SetupMockBot
+import framework.testHttpApplication
+import io.ktor.client.call.*
+import io.ktor.client.request.*
+import net.mamoe.mirai.api.http.adapter.common.StateCode
+import net.mamoe.mirai.api.http.adapter.internal.consts.Paths
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.FriendList
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.LongTargetDTO
+import kotlin.test.Test
+import kotlin.test.assertContains
+import kotlin.test.assertEquals
+
+@ExtendWith(SetupMockBot::class)
+class FriendActionTest {
+
+    @Test
+    fun testDeleteFriend() = testHttpApplication {
+        client.get(Paths.friendList).body<FriendList>().also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertEquals(2, it.data.size)
+
+            val ids = it.data.map { q -> q.id }.toList()
+            assertContains(ids, SetupMockBot.BEST_FRIEND_ID)
+            assertContains(ids, SetupMockBot.WORST_FRIEND_ID)
+        }
+
+        postJsonData<StateCode>(
+            Paths.deleteFriend, LongTargetDTO(
+                target = 987654321L
+            )
+        ).also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+
+        postJsonData<StateCode>(
+            Paths.deleteFriend, LongTargetDTO(
+                target = SetupMockBot.WORST_FRIEND_ID
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+
+        client.get(Paths.friendList).body<FriendList>().also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertEquals(1, it.data.size)
+
+            val ids = it.data.map { q -> q.id }.toList()
+            assertContains(ids, SetupMockBot.BEST_FRIEND_ID)
+        }
+    }
+}

+ 207 - 0
mirai-api-http/src/test/kotlin/integration/action/GroupActionTest.kt

@@ -0,0 +1,207 @@
+package integration.action
+
+import framework.ExtendWith
+import framework.SetupMockBot
+import framework.testHttpApplication
+import io.ktor.client.call.*
+import io.ktor.client.request.*
+import net.mamoe.mirai.api.http.adapter.common.StateCode
+import net.mamoe.mirai.api.http.adapter.internal.consts.Paths
+import net.mamoe.mirai.api.http.adapter.internal.dto.MemberDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.*
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.GroupConfigDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.GroupDetailDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.KickDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.LongTargetDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.MuteDTO
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.contact.nameCardOrNick
+import net.mamoe.mirai.utils.MiraiExperimentalApi
+import kotlin.test.*
+
+@ExtendWith(SetupMockBot::class)
+class GroupActionTest {
+
+    @Test
+    fun testMuteAllAndUnmuteAll() = testHttpApplication {
+        val bot = SetupMockBot.instance()
+        val group = bot.getGroupOrFail(SetupMockBot.BEST_GROUP_ID)
+
+        assertFalse(group.settings.isMuteAll)
+
+        postJsonData<StateCode>(Paths.muteAll, MuteDTO(target = 987654321L)).also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+
+        postJsonData<StateCode>(Paths.muteAll, MuteDTO(target = SetupMockBot.BEST_GROUP_ID)).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertTrue(group.settings.isMuteAll)
+        }
+
+        postJsonData<StateCode>(Paths.unmuteAll, MuteDTO(target = SetupMockBot.BEST_GROUP_ID)).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertFalse(group.settings.isMuteAll)
+        }
+    }
+
+    @Test
+    fun testMuteAndUnmute() = testHttpApplication {
+        val bot = SetupMockBot.instance()
+        val member = bot.getGroupOrFail(SetupMockBot.BEST_GROUP_ID).getOrFail(SetupMockBot.BEST_MEMBER_ID)
+
+        assertFalse(member.isMuted)
+
+        postJsonData<StateCode>(Paths.mute, MuteDTO(target = 987654321L, memberId = member.id, time = 3000)).also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+
+        postJsonData<StateCode>(
+            Paths.mute,
+            MuteDTO(target = SetupMockBot.BEST_GROUP_ID, memberId = 123, time = 3000)
+        ).also {
+            assertEquals(StateCode.NoElement.code, it.code)
+        }
+
+        postJsonData<StateCode>(
+            Paths.mute,
+            MuteDTO(target = SetupMockBot.BEST_GROUP_ID, memberId = member.id, time = 3000)
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertTrue(member.isMuted)
+        }
+
+        postJsonData<StateCode>(
+            Paths.unmute,
+            MuteDTO(target = SetupMockBot.BEST_GROUP_ID, memberId = member.id, time = 3000)
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertFalse(member.isMuted)
+        }
+    }
+
+    @Test
+    fun testKick() = testHttpApplication {
+        val bot = SetupMockBot.instance()
+        val memberId = 123L
+        val group = bot.getGroupOrFail(SetupMockBot.BEST_GROUP_ID)
+            .appendMember(memberId, "kick")
+
+
+        postJsonData<StateCode>(Paths.kick, KickDTO(target = SetupMockBot.BEST_GROUP_ID, memberId = memberId)).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertNull(group[memberId])
+        }
+    }
+
+    @Test
+    fun testQuit() = testHttpApplication {
+        val bot = SetupMockBot.instance()
+        val groupId = 987654321L
+        val group = bot.addGroup(groupId, "to leave")
+
+        postJsonData<StateCode>(Paths.quit, LongTargetDTO(groupId)).also {
+            assertEquals(MemberPermission.OWNER, group.botPermission)
+            // FixMe: Mirai-Mock bug
+            // assertEquals(500, it.code)
+            assertEquals(StateCode.Success.code, it.code)
+            assertNull(bot.getGroup(groupId))
+        }
+    }
+
+    // @Test
+    // fun testSetEssence(): Unit = TODO()
+
+    @Test
+    @OptIn(MiraiExperimentalApi::class)
+    fun testGroupConfig() = testHttpApplication {
+        val bot = SetupMockBot.instance()
+        val group = bot.addGroup(123, "test group")
+
+        client.get(Paths.groupConfig) {
+            parameter("target", group.id)
+        }.body<GroupDetailDTO>().also {
+            assertEquals(group.name, it.name)
+            assertEquals(group.settings.isAnonymousChatEnabled, it.anonymousChat)
+            assertEquals(group.settings.isMuteAll, it.muteAll)
+            assertEquals(group.settings.isAllowMemberInvite, it.allowMemberInvite)
+            assertEquals(group.settings.isAutoApproveEnabled, it.autoApprove)
+            assertEquals(false, it.confessTalk)
+        }
+
+        postJsonData<StateCode>(
+            Paths.groupConfig, GroupConfigDTO(
+                target = group.id,
+                config = GroupDetailDTO(
+                    name = "new name",
+                    allowMemberInvite = true,
+                    // TODO: other config
+                )
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+
+        client.get(Paths.groupConfig) {
+            parameter("target", group.id)
+        }.body<GroupDetailDTO>().also {
+            assertEquals("new name", it.name)
+            assertEquals(true, it.allowMemberInvite)
+        }
+    }
+
+    @Test
+    fun testMemberInfo() = testHttpApplication {
+        val bot = SetupMockBot.instance()
+        val group = bot.addGroup(456, "test group")
+        val member = group.addMember(456, "test member")
+
+        client.get(Paths.memberInfo) {
+            parameter("target", group.id)
+            parameter("memberId", member.id)
+        }.body<MemberDTO>().also {
+            assertEquals(member.id, it.id)
+            assertEquals(member.nameCardOrNick, it.memberName)
+            assertEquals(member.specialTitle, it.specialTitle)
+            assertEquals(member.permission, it.permission)
+        }
+
+        postJsonData<StateCode>(
+            Paths.memberInfo, MemberInfoDTO(
+                target = group.id,
+                memberId = member.id,
+                info = MemberDetailDTO(
+                    name = "new name",
+                    specialTitle = "new special title",
+                )
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+        }
+
+        client.get(Paths.memberInfo) {
+            parameter("target", group.id)
+            parameter("memberId", member.id)
+        }.body<MemberDTO>().also {
+            assertEquals("new name", it.memberName)
+            assertEquals("new special title", it.specialTitle)
+        }
+    }
+
+    @Test
+    fun testMemberAdmin() = testHttpApplication {
+        val bot = SetupMockBot.instance()
+        val group = bot.addGroup(789, "test group")
+        val member = group.addMember(789, "test member")
+
+        assertEquals(MemberPermission.MEMBER, member.permission)
+
+        postJsonData<StateCode>(Paths.memberAdmin, ModifyAdminDTO(
+            target = group.id,
+            memberId = member.id,
+            assign = true
+        )).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertEquals(MemberPermission.ADMINISTRATOR, member.permission)
+        }
+    }
+}

+ 117 - 0
mirai-api-http/src/test/kotlin/integration/action/InfoActionTest.kt

@@ -0,0 +1,117 @@
+package integration.action
+
+import framework.ExtendWith
+import framework.SetupMockBot
+import framework.testHttpApplication
+import io.ktor.client.call.*
+import io.ktor.client.request.*
+import net.mamoe.mirai.api.http.adapter.common.StateCode
+import net.mamoe.mirai.api.http.adapter.internal.consts.Paths
+import net.mamoe.mirai.api.http.adapter.internal.dto.GroupDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.MemberDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.ProfileDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.QQDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.FriendList
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.GroupList
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.MemberList
+import net.mamoe.mirai.contact.getMemberOrFail
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+@ExtendWith(SetupMockBot::class)
+class InfoActionTest {
+
+    @Test
+    fun testFriendLest() = testHttpApplication {
+        client.get(Paths.friendList).body<FriendList>().also {
+            assertEquals(StateCode.Success.code, it.code)
+            val data = it.data
+            assertEquals(2, data.size)
+            assertTrue(it.data.map(QQDTO::id).containsAll(listOf(SetupMockBot.BEST_FRIEND_ID, SetupMockBot.WORST_FRIEND_ID)))
+        }
+    }
+
+    @Test
+    fun testGroupList() = testHttpApplication {
+        client.get(Paths.groupList).body<GroupList>().also {
+            assertEquals(StateCode.Success.code, it.code)
+            val data = it.data
+            assertEquals(2, data.size)
+            assertTrue(it.data.map(GroupDTO::id).containsAll(listOf(SetupMockBot.BEST_GROUP_ID, SetupMockBot.WORST_GROUP_ID)))
+        }
+    }
+
+    @Test
+    fun testMemberList() = testHttpApplication {
+        client.get(Paths.memberList) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+        }.body<MemberList>().also {
+            assertEquals(StateCode.Success.code, it.code)
+            val data = it.data
+            assertEquals(2, data.size)
+            assertTrue(it.data.map(MemberDTO::id).containsAll(listOf(SetupMockBot.BEST_MEMBER_ID, SetupMockBot.GOOD_MEMBER_ID)))
+        }
+    }
+
+    @Test
+    fun testBotProfile() = testHttpApplication {
+        client.get(Paths.botProfile).body<ProfileDTO>().also {
+            val profile = SetupMockBot.instance().asFriend.queryProfile()
+            assertEquals(profile.age, it.age)
+            assertEquals(profile.email, it.email)
+            assertEquals(profile.sex.name, it.sex)
+            assertEquals(profile.qLevel, it.level)
+            assertEquals(profile.nickname, it.nickname)
+            assertEquals(profile.sign, it.sign)
+        }
+    }
+
+    @Test
+    fun testFriendProfile() = testHttpApplication {
+        client.get(Paths.friendProfile) {
+            parameter("target", SetupMockBot.BEST_FRIEND_ID)
+        }.body<ProfileDTO>().also {
+            val profile = SetupMockBot.instance().getFriendOrFail(SetupMockBot.BEST_FRIEND_ID).queryProfile()
+            assertEquals(profile.age, it.age)
+            assertEquals(profile.email, it.email)
+            assertEquals(profile.sex.name, it.sex)
+            assertEquals(profile.qLevel, it.level)
+            assertEquals(profile.nickname, it.nickname)
+            assertEquals(profile.sign, it.sign)
+        }
+    }
+
+    @Test
+    fun testMemberProfile() = testHttpApplication {
+        client.get(Paths.memberProfile) {
+            parameter("target", SetupMockBot.BEST_GROUP_ID)
+            parameter("memberId", SetupMockBot.BEST_MEMBER_ID)
+        }.body<ProfileDTO>().also {
+            val profile = SetupMockBot.instance()
+                .getGroupOrFail(SetupMockBot.BEST_GROUP_ID)
+                .getMemberOrFail(SetupMockBot.BEST_MEMBER_ID).queryProfile()
+            assertEquals(profile.age, it.age)
+            assertEquals(profile.email, it.email)
+            assertEquals(profile.sex.name, it.sex)
+            assertEquals(profile.qLevel, it.level)
+            assertEquals(profile.nickname, it.nickname)
+            assertEquals(profile.sign, it.sign)
+        }
+    }
+
+    @Test
+    fun testUserProfile() = testHttpApplication {
+        client.get(Paths.userProfile) {
+            parameter("target", SetupMockBot.BEST_FRIEND_ID)
+        }.body<ProfileDTO>().also {
+            val profile = SetupMockBot.instance().getFriendOrFail(SetupMockBot.BEST_FRIEND_ID).queryProfile()
+            assertEquals(profile.age, it.age)
+            assertEquals(profile.email, it.email)
+            assertEquals(profile.sex.name, it.sex)
+            assertEquals(profile.qLevel, it.level)
+            assertEquals(profile.nickname, it.nickname)
+            assertEquals(profile.sign, it.sign)
+        }
+    }
+}

+ 201 - 0
mirai-api-http/src/test/kotlin/integration/action/MessageActionTest.kt

@@ -0,0 +1,201 @@
+package integration.action
+
+import framework.ExtendWith
+import framework.SetupMockBot
+import framework.testHttpApplication
+import io.ktor.client.call.*
+import io.ktor.client.request.*
+import kotlinx.coroutines.*
+import net.mamoe.mirai.api.http.adapter.common.StateCode
+import net.mamoe.mirai.api.http.adapter.internal.consts.Paths
+import net.mamoe.mirai.api.http.adapter.internal.dto.*
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.SendDTO
+import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.SendRetDTO
+import kotlin.test.*
+
+@ExtendWith(SetupMockBot::class)
+class MessageActionTest {
+
+    @Test
+    fun testMessageFromId() = testHttpApplication {
+        val msg = postJsonData<SendRetDTO>(
+            Paths.sendFriendMessage, SendDTO(
+                target = SetupMockBot.BEST_FRIEND_ID,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.Success.code, it.code) }
+
+        client.get(Paths.messageFromId) {
+            parameter("target", SetupMockBot.BEST_FRIEND_ID)
+            parameter("messageId", msg.messageId)
+        }.body<EventRestfulResult>().also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertNotNull(it.data)
+            val data = assertIs<FriendMessagePacketDTO>(it.data)
+            assertEquals(SetupMockBot.ID, data.sender.id)
+            assertEquals(2, data.messageChain.size)
+
+            val source = assertIs<MessageSourceDTO>(data.messageChain[0])
+            val plain = assertIs<PlainDTO>(data.messageChain[1])
+
+            assertEquals(msg.messageId, source.id)
+            assertEquals("Hello World", plain.text)
+        }
+    }
+
+    @Test
+    fun testSendFriendMessage() = testHttpApplication {
+
+        // 无指定对象
+        postJsonData<StateCode>(
+            Paths.sendFriendMessage, SendDTO(
+                qq = null,
+                target = null,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.NoElement.code, it.code) }
+
+        // 找不到指定对象
+        postJsonData<StateCode>(
+            Paths.sendFriendMessage, SendDTO(
+                target = 987654321L,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.NoElement.code, it.code) }
+
+        // 正常成功
+        postJsonData<SendRetDTO>(
+            Paths.sendFriendMessage, SendDTO(
+                qq = SetupMockBot.BEST_FRIEND_ID,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertNotEquals(-1, it.messageId)
+        }
+        // 正常成功
+        postJsonData<SendRetDTO>(
+            Paths.sendFriendMessage, SendDTO(
+                target = SetupMockBot.BEST_FRIEND_ID,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertNotEquals(-1, it.messageId)
+        }
+
+        // 处理“陌生人”消息
+        SetupMockBot.instance().addStranger(114514, "Stranger")
+        postJsonData<SendRetDTO>(
+            Paths.sendFriendMessage, SendDTO(
+                qq = 114514,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertNotEquals(-1, it.messageId)
+        }
+    }
+
+    @Test
+    fun testSendGroupMessage() = testHttpApplication {
+        // 无指定对象
+        postJsonData<StateCode>(
+            Paths.sendGroupMessage, SendDTO(
+                group = null,
+                target = null,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.NoElement.code, it.code) }
+
+        // 找不到指定对象
+        postJsonData<StateCode>(
+            Paths.sendGroupMessage, SendDTO(
+                target = 987654321L,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.NoElement.code, it.code) }
+
+        // 正常成功
+        postJsonData<SendRetDTO>(
+            Paths.sendGroupMessage, SendDTO(
+                group = SetupMockBot.BEST_GROUP_ID,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertNotEquals(-1, it.messageId)
+        }
+        // 正常成功
+        postJsonData<SendRetDTO>(
+            Paths.sendGroupMessage, SendDTO(
+                target = SetupMockBot.BEST_GROUP_ID,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertNotEquals(-1, it.messageId)
+        }
+    }
+
+    @Test
+    fun testSendTempMessage() = testHttpApplication {
+        // 无指定对象
+        postJsonData<StateCode>(
+            Paths.sendTempMessage, SendDTO(
+                group = null,
+                qq = null,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.NoElement.code, it.code) }
+
+        // 找不到指定对象
+        postJsonData<StateCode>(
+            Paths.sendTempMessage, SendDTO(
+                group = SetupMockBot.BEST_GROUP_ID,
+                qq = null,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.NoElement.code, it.code) }
+
+        // 找不到指定对象
+        postJsonData<StateCode>(
+            Paths.sendTempMessage, SendDTO(
+                group = null,
+                qq = SetupMockBot.BEST_MEMBER_ID,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.NoElement.code, it.code) }
+
+        // 正常成功
+        postJsonData<SendRetDTO>(
+            Paths.sendTempMessage, SendDTO(
+                group = SetupMockBot.BEST_GROUP_ID,
+                qq = SetupMockBot.BEST_MEMBER_ID,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also {
+            assertEquals(StateCode.Success.code, it.code)
+            assertNotEquals(-1, it.messageId)
+        }
+    }
+
+    @Test
+    fun testSendOtherClientMessage() = testHttpApplication {
+        // 无指定对象
+        postJsonData<StateCode>(
+            Paths.sendTempMessage, SendDTO(
+                target = null,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.NoElement.code, it.code) }
+
+        // 找不到指定对象
+        postJsonData<StateCode>(
+            Paths.sendTempMessage, SendDTO(
+                target = 0,
+                messageChain = listOf(PlainDTO("Hello World"))
+            )
+        ).also { assertEquals(StateCode.NoElement.code, it.code) }
+    }
+}