diff --git a/mirai-api-http/build.gradle.kts b/mirai-api-http/build.gradle.kts index aa5e7e6a..ec7b84f6 100644 --- a/mirai-api-http/build.gradle.kts +++ b/mirai-api-http/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { testImplementation("org.slf4j:slf4j-simple:1.7.32") testImplementation(kotlin("test-junit5")) ktorTest("server-test-host") + ktorTest("server-netty") } val httpVersion: String by rootProject.extra diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/integration/TestMahApplication.kt b/mirai-api-http/src/test/kotlin/framework/TestMahApplication.kt similarity index 89% rename from mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/integration/TestMahApplication.kt rename to mirai-api-http/src/test/kotlin/framework/TestMahApplication.kt index 170a922c..bc4e8649 100644 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/integration/TestMahApplication.kt +++ b/mirai-api-http/src/test/kotlin/framework/TestMahApplication.kt @@ -1,11 +1,13 @@ -package net.mamoe.mirai.api.http.integration +package framework import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.engine.* import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.plugins.websocket.* import io.ktor.client.request.* import io.ktor.http.* +import io.ktor.serialization.kotlinx.* import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* import io.ktor.server.engine.* @@ -13,9 +15,11 @@ import io.ktor.server.routing.* import io.ktor.server.testing.* import io.ktor.util.* import io.ktor.util.pipeline.* +import kotlinx.serialization.json.Json import net.mamoe.mirai.api.http.adapter.MahAdapter import net.mamoe.mirai.api.http.adapter.MahAdapterFactory import net.mamoe.mirai.api.http.adapter.http.router.httpModule +import net.mamoe.mirai.api.http.adapter.webhook.WebhookAdapter import net.mamoe.mirai.api.http.adapter.ws.router.websocketRouteModule import net.mamoe.mirai.api.http.context.MahContextHolder import net.mamoe.mirai.api.http.context.session.manager.DefaultSessionManager @@ -52,7 +56,7 @@ class MahApplicationTestBuilder(private val builder: ApplicationTestBuilder): Cl } fun installWebHookAdapter() { - // TODO + buildAdapter("webhook").enable() } private inline fun buildAdapter(adapter: String): T { @@ -62,6 +66,9 @@ class MahApplicationTestBuilder(private val builder: ApplicationTestBuilder): Cl override val client by lazy { createClient { + install(WebSockets) { + contentConverter = KotlinxWebsocketSerializationConverter(Json) + } install(ContentNegotiation) { json() } diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/util/extend.kt b/mirai-api-http/src/test/kotlin/framework/extend.kt similarity index 96% rename from mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/util/extend.kt rename to mirai-api-http/src/test/kotlin/framework/extend.kt index 0bb45b38..4b4eb7fe 100644 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/util/extend.kt +++ b/mirai-api-http/src/test/kotlin/framework/extend.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.api.http.util +package framework import net.mamoe.mirai.mock.MockBotFactory import org.junit.jupiter.api.extension.BeforeAllCallback diff --git a/mirai-api-http/src/test/kotlin/integration/auth/HttpAuthTest.kt b/mirai-api-http/src/test/kotlin/integration/auth/HttpAuthTest.kt new file mode 100644 index 00000000..2f32a3e9 --- /dev/null +++ b/mirai-api-http/src/test/kotlin/integration/auth/HttpAuthTest.kt @@ -0,0 +1,168 @@ +package integration.auth + +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 net.mamoe.mirai.api.http.context.MahContext +import framework.MahApplicationTestBuilder +import framework.testMahApplication +import framework.SetupMockBot +import integration.withSession +import org.junit.jupiter.api.extension.ExtendWith +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +/** + * 测试 Session 在不同情况下的正确性 + */ +@ExtendWith(SetupMockBot::class) +class HttpAuthTest { + + private val verifyKey = "session test" + private val pathsVerify = Paths.httpPath("verify") + private val pathsBind = Paths.httpPath("bind") + + /** + * 测试单session需要认证 + */ + @Test + fun testSingleSessionOnHttp() = testMahApplication( + verifyKey = verifyKey, + enableVerify = true, + singleMode = true, + ) { + installHttpAdapter() + + getSessionInfoAndExpect(null, StateCode.NotVerifySession) + + postJsonData(pathsVerify, VerifyDTO("wrong $verifyKey")).also { + assertEquals(StateCode.AuthKeyFail.code, it.code) + } + + postJsonData(pathsVerify, VerifyDTO(verifyKey)).also { + assertEquals(StateCode.Success.code, it.code) + } + + getSessionInfoAndExpect(null) + } + + /** + * 测试无需认证的单 Session 模式 + */ + @Test + fun testSingleSessionOnHttpWithoutAuth() = testMahApplication( + verifyKey = verifyKey, + enableVerify = false, + singleMode = true, + ) { + installHttpAdapter() + + // 无需认证的单例 session 模式可以直接访问 + getSessionInfoAndExpect(null) + + postJsonData(pathsVerify, VerifyDTO(verifyKey)).also { + assertEquals(StateCode.Success.code, it.code) + } + + postJsonData(pathsVerify, VerifyDTO("wrong $verifyKey")).also { + assertEquals(StateCode.Success.code, it.code) + } + + getSessionInfoAndExpect(null) + } + + /** + * 测试需认证的通常 Session 模式 + */ + @Test + fun testAuthNormalSessionOnHttp() = testMahApplication( + verifyKey = verifyKey, + enableVerify = true, + singleMode = false, + ) { + installHttpAdapter() + + getSessionInfoAndExpect("nonexistent session", StateCode.IllegalSession) + + postJsonData(pathsVerify, VerifyDTO("wrong $verifyKey")).also { + assertEquals(StateCode.AuthKeyFail.code, it.code) + } + + val verifyRet = postJsonData(pathsVerify, VerifyDTO(verifyKey)).also { + assertEquals(StateCode.Success.code, it.code) + assertNotNull(it.session) + } + + // 认证但未绑定 + getSessionInfoAndExpect(verifyRet.session, errorState = StateCode.NotVerifySession) + + // bind + postJsonData(pathsBind, BindDTO(SetupMockBot.ID)).also { + assertEquals(StateCode.IllegalSession.code, it.code) + } + + // bind with session + postJsonData(pathsBind, BindDTO(SetupMockBot.ID).withSession(verifyRet.session)).also { + assertEquals(StateCode.Success.code, it.code) + } + + getSessionInfoAndExpect(verifyRet.session) + } + + @Test + fun testAuthNormalSessionOnHttpWithAuth() = testMahApplication( + verifyKey = verifyKey, + enableVerify = false, + singleMode = false, + ) { + installHttpAdapter() + + // 非单例 session 模式下,出现找不到 session 异常 + getSessionInfoAndExpect("nonexistent session", StateCode.IllegalSession) + + // 无需认证key,但仍需要通过认证接口获取 session + val verifyRet = postJsonData(pathsVerify, VerifyDTO("Arbitrary $verifyKey")).also { + assertEquals(StateCode.Success.code, it.code) + assertNotNull(it.session) + } + + // 认证但未绑定 + getSessionInfoAndExpect(verifyRet.session, errorState = StateCode.NotVerifySession) + + // use session to bind + postJsonData(pathsBind, BindDTO(SetupMockBot.ID).withSession(verifyRet.session)).also { + assertEquals(StateCode.Success.code, it.code) + } + + getSessionInfoAndExpect(verifyRet.session) + } + + private suspend fun MahApplicationTestBuilder.getSessionInfoAndExpect( + sessionKey: String?, + errorState: StateCode? = null + ) { + if (errorState == null) { + // expect success + val resp = client.get(Paths.sessionInfo) { + parameter("sessionKey", sessionKey) + }.body() + + val ret = resp.data.jsonElementParseOrNull() + + assertNotNull(ret) + assertEquals(sessionKey ?: MahContext.SINGLE_SESSION_KEY, ret.sessionKey) + assertEquals(SetupMockBot.ID, ret.qq.id) + } else { + val ret = client.get(Paths.sessionInfo) { + parameter("sessionKey", sessionKey) + }.body() + + assertNotNull(ret) + assertEquals(errorState.code, ret.code) + } + } +} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/integration/auth/WebSocketAuthTest.kt b/mirai-api-http/src/test/kotlin/integration/auth/WebSocketAuthTest.kt new file mode 100644 index 00000000..cf321f05 --- /dev/null +++ b/mirai-api-http/src/test/kotlin/integration/auth/WebSocketAuthTest.kt @@ -0,0 +1,146 @@ +package integration.auth + +import framework.SetupMockBot +import framework.testMahApplication +import integration.receiveDTO +import integration.withSession +import io.ktor.client.plugins.websocket.* +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.BindDTO +import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyDTO +import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyRetDTO +import net.mamoe.mirai.api.http.context.MahContext +import org.junit.jupiter.api.extension.ExtendWith +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +@ExtendWith(SetupMockBot::class) +class WebSocketAuthTest { + + private val verifyKey = "session test" + private val verifyPath = Paths.httpPath("verify") + private val bindPath = Paths.httpPath("bind") + + /** + * 单例 session 模式下建立 ws 链接 + */ + @Test + fun testSingleSessionOnWs() = testMahApplication( + verifyKey = verifyKey, + enableVerify = true, + singleMode = true, + debug = false, + ) { + installWsAdapter() + + client.ws("/all?verifyKey=wrongVerifyKey") { + receiveDTO().also { + assertEquals(StateCode.AuthKeyFail.code, it.code) + } + } + + client.ws("/all?verifyKey=$verifyKey") { + receiveDTO().also { + assertEquals(MahContext.SINGLE_SESSION_KEY, it.session) + } + } + } + + /** + * 单例 session 模式下无需验证建立链接, 这是最宽松的验证, 完全没有安全性 + */ + @Test + fun testSingleSessionONWsWithoutAuth() = testMahApplication( + verifyKey = verifyKey, + enableVerify = false, + singleMode = true, + debug = false, + ) { + installWsAdapter() + + client.ws("/all") { + receiveDTO().also { + assertEquals(MahContext.SINGLE_SESSION_KEY, it.session) + } + } + } + + /** + * 测试需要认证的 session 模式建立链接, 这是最常用的方案 + */ + @Test + fun testAuthNormalSessionOnWs() = testMahApplication( + verifyKey = verifyKey, + enableVerify = true, + singleMode = false, + debug = false, + ) { + installHttpAdapter() + installWsAdapter() + + // 错误 verify key + client.ws("/all?verifyKey=wrongVerifyKey") { + receiveDTO().also { + assertEquals(StateCode.AuthKeyFail.code, it.code) + } + } + + // 不绑定账号 + client.ws("/all?verifyKey=$verifyKey") { + receiveDTO().also { + assertEquals(StateCode.InvalidParameter.code, it.code) + } + } + + // 无法绑定账号(绑定错误账号) + client.ws("/all?verifyKey=$verifyKey&qq=${SetupMockBot.ID + 1}") { + receiveDTO().also { + assertEquals(StateCode.NoBot.code, it.code) + } + } + + // 通过已有 session 绑定 + + // 通过 http 创建一个session + val session = postJsonData(verifyPath, VerifyDTO(verifyKey)).session + + // 通过 ws 绑定错误 session + client.ws("/all?verifyKey=$verifyKey&sessionKey=wrong$session") { + receiveDTO().also { + assertEquals(StateCode.IllegalSession.code, it.code) + } + } + + // 通过 ws 绑定已有未认证 session + client.ws("/all?verifyKey=$verifyKey&sessionKey=$session") { + receiveDTO().also { + assertEquals(StateCode.NotVerifySession.code, it.code) + } + } + + // 通过 http 认证 session + postJsonData(bindPath, BindDTO(SetupMockBot.ID).withSession(session)) + + var ret: VerifyRetDTO? = null + // 通过 ws 绑定已有已认证 session + client.ws("/all?verifyKey=$verifyKey&sessionKey=$session") { + receiveDTO().also { + assertEquals(StateCode.Success.code, it.code) + assertNotEquals(MahContext.SINGLE_SESSION_KEY, it.session) + ret = it + } + } + + // 通过 ws 创建新 session 并绑定 + client.ws("/all?verifyKey=$verifyKey&qq=${SetupMockBot.ID}") { + receiveDTO().also { + assertEquals(StateCode.Success.code, it.code) + assertNotEquals(MahContext.SINGLE_SESSION_KEY, it.session) + // not same session + assertNotEquals(ret?.session, it.session) + } + } + } +} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/integration/helper.kt b/mirai-api-http/src/test/kotlin/integration/helper.kt new file mode 100644 index 00000000..f94cb118 --- /dev/null +++ b/mirai-api-http/src/test/kotlin/integration/helper.kt @@ -0,0 +1,21 @@ +package integration + +import io.ktor.client.plugins.websocket.* +import net.mamoe.mirai.api.http.adapter.internal.dto.AuthedDTO +import net.mamoe.mirai.api.http.adapter.internal.dto.DTO +import net.mamoe.mirai.api.http.adapter.internal.serializer.jsonElementParseOrNull +import net.mamoe.mirai.api.http.adapter.ws.dto.WsOutgoing +import kotlin.reflect.jvm.javaField + +internal fun T.withSession(sessionKey: String): T { + this::sessionKey.javaField?.apply { + isAccessible = true + set(this@withSession, sessionKey) + } + return this +} + +internal suspend inline fun DefaultClientWebSocketSession.receiveDTO(): T { + val outgoing = receiveDeserialized() + return outgoing.data.jsonElementParseOrNull() ?: throw IllegalStateException("receiveDTO failed") +} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/http/HttpSessionLifeCycle.kt b/mirai-api-http/src/test/kotlin/integration/lifecycle/HttpSessionLifeCycleTest.kt similarity index 69% rename from mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/http/HttpSessionLifeCycle.kt rename to mirai-api-http/src/test/kotlin/integration/lifecycle/HttpSessionLifeCycleTest.kt index a7897618..2203a47d 100644 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/http/HttpSessionLifeCycle.kt +++ b/mirai-api-http/src/test/kotlin/integration/lifecycle/HttpSessionLifeCycleTest.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.api.http.request.http +package integration.lifecycle import kotlinx.coroutines.isActive import net.mamoe.mirai.api.http.adapter.common.StateCode @@ -18,15 +18,15 @@ import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyDTO import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyRetDTO import net.mamoe.mirai.api.http.adapter.internal.serializer.toJson import net.mamoe.mirai.api.http.context.MahContextHolder -import net.mamoe.mirai.api.http.request.startAdapter -import net.mamoe.mirai.api.http.util.ExtendWith -import net.mamoe.mirai.api.http.util.SetupMockBot -import net.mamoe.mirai.api.http.util.withSession +import framework.testMahApplication +import framework.ExtendWith +import framework.SetupMockBot +import integration.withSession import kotlin.test.* @ExtendWith(SetupMockBot::class) -open class HttpSessionLifeCycle { +open class HttpSessionLifeCycleTest { private val verifyKey = "HttpSessionLifeCycle" private val verifyPath = Paths.httpPath("verify") @@ -34,24 +34,25 @@ open class HttpSessionLifeCycle { private val releasePath = Paths.httpPath("release") @Test - fun testReleaseSession() = startAdapter( - "http", + fun testReleaseSession() = testMahApplication( verifyKey = verifyKey, enableVerify = true, singleMode = false, ) { - var data = VerifyDTO(verifyKey).toJson() - val verifyRet = post(verifyPath, data) - assertNotNull(verifyRet.session) + installHttpAdapter() + + val verifyRet = postJsonData(verifyPath, VerifyDTO(verifyKey)).also { + assertEquals(StateCode.Success.code, it.code) + } val session = MahContextHolder[verifyRet.session] assertNotNull(session) assertFalse(session.isAuthed) assertFalse(session.isHttpSession()) - data = BindDTO(SetupMockBot.ID).withSession(verifyRet.session).toJson() - val bindRet = post(bindPath, data) - assertEquals(StateCode.Success.code, bindRet.code) + postJsonData(bindPath, BindDTO(SetupMockBot.ID).withSession(verifyRet.session).toJson()).also { + assertEquals(StateCode.Success.code, it.code) + } val authedSession = MahContextHolder[verifyRet.session] assertNotNull(authedSession) @@ -62,9 +63,9 @@ open class HttpSessionLifeCycle { // same object assertSame(session, authedSession) - data = BindDTO(SetupMockBot.ID).withSession(verifyRet.session).toJson() - val releaseRet = post(releasePath, data) - assertEquals(StateCode.Success.code, releaseRet.code) + postJsonData(releasePath, BindDTO(SetupMockBot.ID).withSession(verifyRet.session).toJson()).also { + assertEquals(StateCode.Success.code, it.code) + } val releasedSession = MahContextHolder[verifyRet.session] assertNull(releasedSession) diff --git a/mirai-api-http/src/test/kotlin/integration/lifecycle/WsSessionLifeCycle.kt b/mirai-api-http/src/test/kotlin/integration/lifecycle/WsSessionLifeCycle.kt new file mode 100644 index 00000000..1e509359 --- /dev/null +++ b/mirai-api-http/src/test/kotlin/integration/lifecycle/WsSessionLifeCycle.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package integration.lifecycle + +import framework.SetupMockBot +import framework.testMahApplication +import integration.receiveDTO +import integration.withSession +import io.ktor.client.plugins.websocket.* +import io.ktor.websocket.* +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +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.BindDTO +import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyDTO +import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyRetDTO +import net.mamoe.mirai.api.http.context.MahContextHolder +import org.junit.jupiter.api.extension.ExtendWith +import kotlin.test.* + +@ExtendWith(SetupMockBot::class) +class WsSessionLifeCycle { + + private val verifyKey = "HttpSessionLifeCycle" + private val verifyPath = Paths.httpPath("verify") + private val bindPath = Paths.httpPath("bind") + private val releasePath = Paths.httpPath("release") + + @Test + fun testDisconnectSession() = testMahApplication( + verifyKey = verifyKey, + enableVerify = true, + singleMode = false, + ) { + installWsAdapter() + + // 通过 ws 创建新 session 并绑定 + client.ws("/all?verifyKey=$verifyKey&qq=${SetupMockBot.ID}") { + val ret = receiveDTO() + assertNotNull(ret.session) + + // socket 由客户端主动断开, 服务端需要一定时间感知 + close() + delay(1000) + + val session = MahContextHolder[ret.session] + assertNull(session) + } + } + + @Test + fun testDisconnectSessionFromHttp() = testMahApplication( + verifyKey = verifyKey, + enableVerify = true, + singleMode = false, + ) { + installHttpAdapter() + installWsAdapter() + + // 创建 http session + val verifyRet = postJsonData(verifyPath, VerifyDTO(verifyKey)) + assertNotNull(verifyRet.session) + + val session = MahContextHolder[verifyRet.session] + assertNotNull(session) + assertEquals(0, session.getRefCount()) + + // 认证 http session 并引用 + postJsonData(bindPath, BindDTO(SetupMockBot.ID).withSession(verifyRet.session)).also { + assertEquals(StateCode.Success.code, it.code) + assertEquals(1, session.getRefCount()) + } + + // 通过 websocket 复用 session + client.ws("/all?verifyKey=$verifyKey&sessionKey=${verifyRet.session}") { + val wsRet = receiveDTO() + assertEquals(verifyRet.session, wsRet.session) + assertEquals(2, session.getRefCount()) + + // socket 由客户端主动断开, 服务端需要一定时间感知 + close() + delay(1000) + + // websocket 断开时, session 引用依旧被 http 引用保留 + assertEquals(1, session.getRefCount()) + assertFalse(session.isClosed) + assertTrue(session.isActive) + } + + // http 释放 session + postJsonData(releasePath, BindDTO(SetupMockBot.ID).withSession(verifyRet.session)).also { + assertEquals(StateCode.Success.code, it.code) + assertEquals(0, session.getRefCount()) + + // session 被回收 + assertTrue(session.isClosed) + assertFalse(session.isActive) + } + } +} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/launch/adapter/AdapterLaunch.kt b/mirai-api-http/src/test/kotlin/launch/adapter/AdapterLaunch.kt deleted file mode 100644 index 0defa6b4..00000000 --- a/mirai-api-http/src/test/kotlin/launch/adapter/AdapterLaunch.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package launch.adapter - -import kotlinx.coroutines.runBlocking - -object HttpAdapterLaunch : LaunchTester() { - - @JvmStatic - fun main(args: Array) { - runBlocking { - runServer("http") - } - } -} - -object WsAdapterLaunch : LaunchTester() { - - @JvmStatic - fun main(args: Array) { - runBlocking { - runServer("ws") - } - } -} - -object WebhookAdapterLaunch : LaunchTester() { - - @JvmStatic - fun main(args: Array) { - runBlocking { - runServer("webhook") - } - } -} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/launch/adapter/LaunchTester.kt b/mirai-api-http/src/test/kotlin/launch/adapter/LaunchTester.kt deleted file mode 100644 index 764f85dc..00000000 --- a/mirai-api-http/src/test/kotlin/launch/adapter/LaunchTester.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package launch.adapter - -import net.mamoe.mirai.BotFactory -import net.mamoe.mirai.api.http.MahPluginImpl -import net.mamoe.mirai.api.http.adapter.MahAdapterFactory -import net.mamoe.mirai.api.http.context.session.manager.DefaultSessionManager -import net.mamoe.mirai.api.http.setting.MainSetting -import net.mamoe.mirai.utils.BotConfiguration -import java.io.File -import java.util.* - -abstract class LaunchTester { - - private val properties: Properties by lazy { - Properties().apply { - File("launcher.properties").inputStream().use { load(it) } - } - } - - private val enable: Boolean get() = properties.getProperty("enable").toBoolean() - - private val qq: Long get() = properties.getProperty("qq").toLong() - - private val password: String get() = properties.getProperty("password") - - protected suspend fun runServer(vararg adapters: String) { - if (!enable) return - - - with(MainSetting) { - - // 创建上下文启动 mah 插件 - MahPluginImpl.start { - sessionManager = DefaultSessionManager(verifyKey, this) - enableVerify = false - singleMode = true - debug = true - - for (adapter in adapters) { - MahAdapterFactory.build(adapter)?.let(this::plusAssign) - } - } - } - - val bot = BotFactory.newBot(qq, password) { - fileBasedDeviceInfo("device.json") - - protocol = BotConfiguration.MiraiProtocol.ANDROID_PHONE - } - - bot.login() - bot.join() - } -} diff --git a/mirai-api-http/src/test/kotlin/launch/httpserver/EchoHttpServer.kt b/mirai-api-http/src/test/kotlin/launch/httpserver/EchoHttpServer.kt deleted file mode 100644 index d626f45c..00000000 --- a/mirai-api-http/src/test/kotlin/launch/httpserver/EchoHttpServer.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package launch.httpserver - -import io.ktor.server.application.* -import io.ktor.http.* -import io.ktor.server.routing.* -import io.ktor.server.cio.* -import io.ktor.server.engine.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.util.* -import org.slf4j.helpers.NOPLogger - -object EchoHttpServer { - - @JvmStatic - fun main(args: Array) { - embeddedServer(CIO, applicationEngineEnvironment { - - log = NOPLogger.NOP_LOGGER - - module { - routing { - post("/") { - val receiveText = call.receiveText() - call.respondText(receiveText, call.defaultTextContentType(ContentType.Application.Json)) - println(receiveText) - println(call.request.headers.toMap()) - println("===================") - } - } - } - - connector { - host = "localhost" - port = 9999 - } - }).start(true) - } -} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/internal/serializer/KtorParameterFormatTest.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/internal/serializer/KtorParameterFormatTest.kt index d770d749..bf6bc742 100644 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/internal/serializer/KtorParameterFormatTest.kt +++ b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/internal/serializer/KtorParameterFormatTest.kt @@ -66,14 +66,4 @@ class KtorParameterFormatTest { val target: Long, val memberIds: List? ) - - @Test - fun testMessage() { - val param = parametersOf( - "target" to listOf("123123"), - "messageId" to listOf("11111"), - ) - val dto = KtorParameterFormat.DEFAULT.decode(param, MessageIdDTO.serializer()) - println(dto) - } } \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/internal/serializer/SerializationTest.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/internal/serializer/SerializationTest.kt index f9e243ed..e919c5ae 100644 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/internal/serializer/SerializationTest.kt +++ b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/adapter/internal/serializer/SerializationTest.kt @@ -26,8 +26,6 @@ class SerializationTest { */ @Test fun testMessageChain() { - println(System.getenv()) - val chain = groupMessageDTO() val json = chain.toJson() assertEquals(chain, json.jsonParseOrNull(), "messageChain 序列化异常") diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/environment.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/environment.kt deleted file mode 100644 index cf57b96b..00000000 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/environment.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.api.http.request - -import io.ktor.client.* -import io.ktor.client.engine.okhttp.* -import io.ktor.client.plugins.websocket.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.server.testing.* -import io.ktor.util.* -import io.ktor.websocket.* -import kotlinx.coroutines.runBlocking -import net.mamoe.mirai.api.http.MahPluginImpl -import net.mamoe.mirai.api.http.adapter.MahAdapter -import net.mamoe.mirai.api.http.adapter.MahAdapterFactory -import net.mamoe.mirai.api.http.adapter.internal.dto.DTO -import net.mamoe.mirai.api.http.adapter.internal.serializer.jsonElementParseOrNull -import net.mamoe.mirai.api.http.adapter.internal.serializer.jsonParseOrNull -import net.mamoe.mirai.api.http.adapter.internal.serializer.toJson -import net.mamoe.mirai.api.http.adapter.internal.serializer.toJsonElement -import net.mamoe.mirai.api.http.adapter.ws.dto.WsIncoming -import net.mamoe.mirai.api.http.adapter.ws.dto.WsOutgoing -import net.mamoe.mirai.api.http.context.MahContext -import net.mamoe.mirai.api.http.context.session.manager.DefaultSessionManager -import java.net.InetSocketAddress -import java.net.Socket -import kotlin.reflect.KProperty -import kotlin.reflect.KProperty1 -import kotlin.reflect.jvm.isAccessible -import kotlin.reflect.jvm.javaField - -internal fun startAdapter( - vararg adapters: String, - verifyKey: String = "", - enableVerify: Boolean = true, - singleMode: Boolean = false, - debug: Boolean = false, - operation: suspend AdapterOperation.() -> Unit, -) { - val port = getRandomPort() - - // launch adapter - MahPluginImpl.start { - sessionManager = DefaultSessionManager(verifyKey, this) - this.enableVerify = enableVerify - this.singleMode = singleMode - this.debug = debug - - // clean adapter list in context - @Suppress("UNCHECKED_CAST") - MahContext::class.members.first { it.name == "adapters" }.let { - it as KProperty> - it.call(this).clear() - } - - @Suppress("UNCHECKED_CAST") - for (adapter in adapters) { - val mahAdapter = MahAdapterFactory.build(adapter) ?: continue - - val setting = mahAdapter::class.members.firstOrNull { it.name == "setting" }?.let { - it as KProperty - it.isAccessible = true - it.getter.call(mahAdapter) - } ?: continue - - setting::class.members.firstOrNull { it.name == "port" }?.let { - it as KProperty1<*, Int> - it.isAccessible = true - it.javaField?.set(setting, port) - } - - this.plusAssign(mahAdapter) - } - } - - // invoke operations - runBlocking { - val operations = AdapterOperation(port) - operations.operation() - } - - MahPluginImpl.stop() -} - -internal class AdapterOperation(val port: Int) { - - - private val client by lazy { HttpClient(OkHttp) } - private val wsClient by lazy { HttpClient(OkHttp) { install(WebSockets) } } - - suspend inline fun get(path: String, query: Map = emptyMap()): T { - val content = client.get(path) { - port = this@AdapterOperation.port - query.forEach { (k, v) -> parameter(k, v) } - }.bodyAsText() - return content.jsonParseOrNull()!! - } - - suspend inline fun post(path: String, data: String): T { - val context = client.post(path) { - port = this@AdapterOperation.port - setBody(data) - }.bodyAsText() - return context.jsonParseOrNull()!! - } - - fun wsConnect(query: Map, operation: suspend WsAdapterOperation.() -> R): R { - return runBlocking { - var ret: R? = null - wsClient.ws({ - url("ws", "localhost", this@AdapterOperation.port, "all") - query.forEach { (k, v) -> parameter(k, v) } - }) { - ret = operation.invoke(WsAdapterOperation(this)) - } - return@runBlocking ret!! - } - } -} - -internal class WsAdapterOperation(val session: WebSocketSession) { - - suspend inline fun receiveDTO(): T? { - val frame = session.incoming.receive() - val content = String(frame.data) - val pkg: WsOutgoing? = content.jsonParseOrNull() - return pkg?.data?.jsonElementParseOrNull() - } - - suspend inline fun sendDTO(dto: T?, command: String, subCommand: String = "") { - val pkg = WsIncoming("0", command, subCommand, dto?.toJsonElement()) - val frame = Frame.Text(pkg.toJson()) - session.outgoing.send(frame) - } -} - -internal fun getRandomPort(): Int { - val socket = Socket() - socket.bind(InetSocketAddress(0)) - val port = socket.localPort - socket.close() - return port -} diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/http/HttpAuthTest.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/http/HttpAuthTest.kt deleted file mode 100644 index 9f274e6e..00000000 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/http/HttpAuthTest.kt +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.api.http.request.http - -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 net.mamoe.mirai.api.http.adapter.internal.serializer.toJson -import net.mamoe.mirai.api.http.context.MahContext -import net.mamoe.mirai.api.http.request.AdapterOperation -import net.mamoe.mirai.api.http.request.startAdapter -import net.mamoe.mirai.api.http.util.ExtendWith -import net.mamoe.mirai.api.http.util.SetupMockBot -import net.mamoe.mirai.api.http.util.withSession -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -/** - * 测试 Session 在不同情况下的正确性 - */ -@ExtendWith(SetupMockBot::class) -class HttpAuthTest { - - private val verifyKey = "session test" - private val verifyPath = Paths.httpPath("verify") - private val bindPath = Paths.httpPath("bind") - private val action = Paths.sessionInfo - - /** - * 测试单session需要认证 - */ - @Test - fun testSingleSessionOnHttp() = startAdapter( - "http", - verifyKey = verifyKey, - enableVerify = true, - singleMode = true, - debug = false, - ) { - // 单例 session 未认证 - getSessionInfoAndExpect(null, StateCode.NotVerifySession) - - val data = VerifyDTO("wrong $verifyKey").toJson() - val ret = post(verifyPath, data) - assertEquals(StateCode.AuthKeyFail.code, ret.code) - - val correctData = VerifyDTO(verifyKey).toJson() - val correctRet = post(Paths.httpPath("verify"), correctData) - assertEquals(StateCode.Success.code, correctRet.code) - - getSessionInfoAndExpect(null) - } - - /** - * 测试无需认证的单 Session 模式 - */ - @Test - fun testSingleSessionOnHttpWithoutAuth() = startAdapter("http", - verifyKey = verifyKey, - enableVerify = false, - singleMode = true, - debug = false, - ) { - // 无需认证的单例 session 模式可以直接访问 - getSessionInfoAndExpect(null) - - val data = VerifyDTO(verifyKey).toJson() - val ret = post(verifyPath, data) - assertEquals(StateCode.Success.code, ret.code) - - val wrongData = VerifyDTO("wrong $verifyKey").toJson() - val wrongRet = post(verifyPath, wrongData) - assertEquals(StateCode.Success.code, wrongRet.code) - - getSessionInfoAndExpect(null) - } - - /** - * 测试需认证的通常 Session 模式 - */ - @Test - fun testAuthNormalSessionOnHttp() = startAdapter("http", - verifyKey = verifyKey, - enableVerify = true, - singleMode = false, - debug = false, - ) { - // 非单例 session 模式下,出现找不到 session 异常 - getSessionInfoAndExpect("nonexistent session", StateCode.IllegalSession) - - var data = VerifyDTO("wrong $verifyKey").toJson() - val wrongRet = post(verifyPath, data) - assertEquals(StateCode.AuthKeyFail.code, wrongRet.code) - - data = VerifyDTO(verifyKey).toJson() - val verifyRet = post(verifyPath, data) - assertEquals(StateCode.Success.code, verifyRet.code) - assertNotNull(verifyRet.session) - - // 认证但未绑定 - getSessionInfoAndExpect(verifyRet.session, errorState = StateCode.NotVerifySession) - - // use session to bind - data = BindDTO(SetupMockBot.ID).toJson() - var bindRet: StateCode = post(bindPath, data) - assertEquals(StateCode.IllegalSession.code, bindRet.code) - - data = BindDTO(SetupMockBot.ID).withSession(verifyRet.session).toJson() - bindRet = post(bindPath, data) - assertEquals(StateCode.Success.code, bindRet.code) - - getSessionInfoAndExpect(verifyRet.session) - } - - @Test - fun testAuthNormalSessionOnHttpWithAuth() = startAdapter("http", - verifyKey = verifyKey, - enableVerify = false, - singleMode = false, - debug = false, - ) { - // 非单例 session 模式下,出现找不到 session 异常 - getSessionInfoAndExpect("nonexistent session", StateCode.IllegalSession) - - // 无需认证key,但仍需要通过认证接口获取 session - var data = VerifyDTO("Arbitrary $verifyKey").toJson() - val verifyRet = post(verifyPath, data) - assertEquals(StateCode.Success.code, verifyRet.code) - assertNotNull(verifyRet.session) - - // 认证但未绑定 - getSessionInfoAndExpect(verifyRet.session, errorState = StateCode.NotVerifySession) - - // use session to bind - data = BindDTO(SetupMockBot.ID).withSession(verifyRet.session).toJson() - val bindRet = post(bindPath, data) - assertEquals(StateCode.Success.code, bindRet.code) - - getSessionInfoAndExpect(verifyRet.session) - } - - private suspend fun AdapterOperation.getSessionInfoAndExpect(sessionKey: String?, errorState: StateCode? = null) { - val query = sessionKey?.let { - mapOf("sessionKey" to it) - } ?: emptyMap() - - if (errorState == null) { - val ret = get(action, query) - .data.jsonElementParseOrNull() - assertNotNull(ret) - assertEquals(sessionKey ?: MahContext.SINGLE_SESSION_KEY, ret.sessionKey) - assertEquals(SetupMockBot.ID, ret.qq.id) - } else { - val ret = get(action, query) - assertEquals(errorState.code, ret.code) - } - } - -} diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/webhook/TestWebhookTimeout.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/webhook/TestWebhookTimeout.kt deleted file mode 100644 index 29552492..00000000 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/webhook/TestWebhookTimeout.kt +++ /dev/null @@ -1,88 +0,0 @@ -package net.mamoe.mirai.api.http.request.webhook - -import io.ktor.server.application.* -import io.ktor.server.cio.* -import io.ktor.server.engine.* -import io.ktor.server.request.* -import io.ktor.server.routing.* -import io.ktor.server.testing.* -import kotlinx.coroutines.delay -import net.mamoe.mirai.Bot -import net.mamoe.mirai.api.http.adapter.http.router.respondDTO -import net.mamoe.mirai.api.http.adapter.internal.consts.Paths -import net.mamoe.mirai.api.http.adapter.internal.dto.parameter.LongTargetDTO -import net.mamoe.mirai.api.http.adapter.internal.serializer.toJsonElement -import net.mamoe.mirai.api.http.adapter.webhook.dto.WebhookPacket -import net.mamoe.mirai.api.http.request.startAdapter -import net.mamoe.mirai.api.http.setting.MainSetting -import net.mamoe.mirai.api.http.util.ExtendWith -import net.mamoe.mirai.api.http.util.SetupMockBot -import net.mamoe.mirai.console.data.findBackingFieldValue -import net.mamoe.mirai.console.util.ConsoleExperimentalApi -import net.mamoe.mirai.mock.MockBot -import net.mamoe.mirai.mock.utils.broadcastMockEvents -import net.mamoe.yamlkt.Yaml -import net.mamoe.yamlkt.YamlElement -import kotlin.test.Test -import kotlin.test.assertFails - -@ExtendWith(SetupMockBot::class) -class TestWebhookTimeout { - - private val verifyKey = "TestWebhookTimeout" - - @OptIn(ConsoleExperimentalApi::class) - @Test - fun testClientTimeout() { - val serverPort = 9999 - - // hijacked MainSettings - val settings = MainSetting.findBackingFieldValue>("adapterSettings") - val s = settings?.value as MutableMap - s["webhook"] = Yaml.decodeYamlFromString(""" - destinations: ["http://localhost:9999/hook"] - """.trimIndent()) - - - // launch server - embeddedServer(CIO, applicationEngineEnvironment { - connector { - host = "localhost" - port = serverPort - } - - modules.add { - routing { - post("/hook") { - assertFails { - println("webhook server receive: ${call.receiveText()}") - delay(11_000) - call.respondDTO(WebhookPacket( - Paths.deleteFriend, - LongTargetDTO(SetupMockBot.FRIEND_ID).toJsonElement() - )) - } - } - } - } - }).start(wait = false) - - // run adapter - startAdapter( - "webhook", - verifyKey = verifyKey, - enableVerify = true, - singleMode = false, - debug = true, - ) { - - val bot = Bot.getInstance(SetupMockBot.ID) as MockBot - - broadcastMockEvents { - bot.getFriend(SetupMockBot.FRIEND_ID)?.says("test") - } - - delay(11_000) - } - } -} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/ws/WsAuthTest.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/ws/WsAuthTest.kt deleted file mode 100644 index 853cbae5..00000000 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/ws/WsAuthTest.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.api.http.request.ws - -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.BindDTO -import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyDTO -import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyRetDTO -import net.mamoe.mirai.api.http.adapter.internal.serializer.toJson -import net.mamoe.mirai.api.http.context.MahContext -import net.mamoe.mirai.api.http.request.startAdapter -import net.mamoe.mirai.api.http.util.ExtendWith -import net.mamoe.mirai.api.http.util.SetupMockBot -import net.mamoe.mirai.api.http.util.withSession -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull - -@ExtendWith(SetupMockBot::class) -class WsAuthTest { - - private val verifyKey = "session test" - private val verifyPath = Paths.httpPath("verify") - private val bindPath = Paths.httpPath("bind") - - /** - * 单例 session 模式下建立 ws 链接 - */ - @Test - fun testSingleSessionOnWs() = startAdapter( - "ws", - verifyKey = verifyKey, - enableVerify = true, - singleMode = true, - debug = false, - ) { - wsConnect(mapOf("verifyKey" to "wrong $verifyKey")) { - val stateCode = receiveDTO() - assertEquals(StateCode.AuthKeyFail.code, stateCode?.code) - } - - wsConnect(mapOf("verifyKey" to verifyKey)) { - val ret = receiveDTO() - assertEquals(MahContext.SINGLE_SESSION_KEY, ret?.session) - } - } - - /** - * 单例 session 模式下无需验证建立链接, 这是最宽松的验证, 完全没有安全性 - */ - @Test - fun testSingleSessionONWsWithoutAuth() = startAdapter( - "ws", - verifyKey = verifyKey, - enableVerify = false, - singleMode = true, - debug = false, - ) { - // connect anyway - wsConnect(emptyMap()) { - val ret = receiveDTO() - assertEquals(MahContext.SINGLE_SESSION_KEY, ret?.session) - } - } - - /** - * 测试需要认证的 session 模式建立链接, 这是最常用的方案 - */ - @Test - fun testAuthNormalSessionOnWs() = startAdapter( - "ws", "http", - verifyKey = verifyKey, - enableVerify = true, - singleMode = false, - debug = false, - ) { - // 错误 verify key - wsConnect(mapOf("verifyKey" to "wrong $verifyKey")) { - val stateCode = receiveDTO() - assertEquals(StateCode.AuthKeyFail.code, stateCode?.code) - } - - // 不绑定账号 - wsConnect(mapOf("verifyKey" to verifyKey)) { - val stateCode = receiveDTO() - assertEquals(StateCode.InvalidParameter.code, stateCode?.code) - } - - // 无法绑定账号(绑定错误账号) - wsConnect(mapOf("verifyKey" to verifyKey, "qq" to "${SetupMockBot.ID + 1}")) { - val stateCode = receiveDTO() - assertEquals(StateCode.NoBot.code, stateCode?.code) - } - - // 通过已有 session 绑定 - - // 通过 http 创建一个session - val session = post(verifyPath, VerifyDTO(verifyKey).toJson()).session - - // 通过 ws 绑定错误 session - wsConnect(mapOf("verifyKey" to verifyKey, "sessionKey" to "wrong $session")) { - val stateCode = receiveDTO() - assertEquals(StateCode.IllegalSession.code, stateCode?.code) - } - - // 通过 ws 绑定已有未认证 session - wsConnect(mapOf("verifyKey" to verifyKey, "sessionKey" to session)) { - val stateCode = receiveDTO() - assertEquals(StateCode.NotVerifySession.code, stateCode?.code) - } - - // 通过 http 认证 session - post(bindPath, BindDTO(SetupMockBot.ID).withSession(session).toJson()) - - // 通过 ws 绑定已有已认证 session - val ret = wsConnect(mapOf("verifyKey" to verifyKey, "sessionKey" to session)) { - val ret = receiveDTO() - assertEquals(StateCode.Success.code, ret?.code) - assertNotNull(ret?.session) - assertNotEquals(MahContext.SINGLE_SESSION_KEY, ret?.session) - return@wsConnect ret - } - - // 通过 ws 创建新 session 并绑定 - wsConnect(mapOf("verifyKey" to verifyKey, "qq" to "${SetupMockBot.ID}")) { - val wsRet = receiveDTO() - assertEquals(StateCode.Success.code, wsRet?.code) - assertNotNull(wsRet?.session) - assertNotEquals(MahContext.SINGLE_SESSION_KEY, wsRet?.session) - // not same session - assertNotEquals(ret?.session, wsRet?.session) - } - } -} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/ws/WsSessionLifeCycle.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/ws/WsSessionLifeCycle.kt deleted file mode 100644 index 35c0abff..00000000 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/request/ws/WsSessionLifeCycle.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.api.http.request.ws - -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -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.BindDTO -import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyDTO -import net.mamoe.mirai.api.http.adapter.internal.dto.VerifyRetDTO -import net.mamoe.mirai.api.http.adapter.internal.serializer.toJson -import net.mamoe.mirai.api.http.context.MahContextHolder -import net.mamoe.mirai.api.http.request.startAdapter -import net.mamoe.mirai.api.http.util.SetupMockBot -import net.mamoe.mirai.api.http.util.withSession -import org.junit.jupiter.api.extension.ExtendWith -import kotlin.test.* - -@ExtendWith(SetupMockBot::class) -class WsSessionLifeCycle { - - private val verifyKey = "HttpSessionLifeCycle" - private val verifyPath = Paths.httpPath("verify") - private val bindPath = Paths.httpPath("bind") - private val releasePath = Paths.httpPath("release") - - @Test - fun testDisconnectSession() = startAdapter( - "ws", - verifyKey = verifyKey, - enableVerify = true, - singleMode = false, - ) { - // 通过 ws 创建新 session 并绑定 - val sessionKey = wsConnect(mapOf("verifyKey" to verifyKey, "qq" to "${SetupMockBot.ID}")) { - val wsRet = receiveDTO() - val sessionKey = wsRet?.session - assertNotNull(sessionKey) - - return@wsConnect sessionKey - } - - // socket 由客户端主动断开, 服务端需要一定时间感知 - delay(1000) - - // after disconnect - val session = MahContextHolder[sessionKey] - assertNull(session) - } - - @Test - fun testDisconnectSessionFromHttp() = startAdapter( - "ws", "http", - verifyKey = verifyKey, - enableVerify = true, - singleMode = false, - ) { - // 创建 http session - var data = VerifyDTO(verifyKey).toJson() - val verifyRet = post(verifyPath, data) - assertNotNull(verifyRet.session) - - val session = MahContextHolder[verifyRet.session] - assertNotNull(session) - assertEquals(0, session.getRefCount()) - - // 认证 http session 并引用 - data = BindDTO(SetupMockBot.ID).withSession(verifyRet.session).toJson() - val bindRet = post(bindPath, data) - assertEquals(StateCode.Success.code, bindRet.code) - assertEquals(1, session.getRefCount()) - - // 通过 websocket 复用 session - wsConnect(mapOf("verifyKey" to verifyKey, "sessionKey" to verifyRet.session)) { - val wsRet = receiveDTO() - val sessionKey = wsRet?.session - assertEquals(verifyRet.session, sessionKey) - } - - // socket 由客户端主动断开, 服务端需要一定时间感知 - delay(1000) - - // websocket 断开时, session 引用依旧被 http 引用保留 - assertEquals(1, session.getRefCount()) - assertFalse(session.isClosed) - assertTrue(session.isActive) - - // http 释放 session - data = BindDTO(SetupMockBot.ID).withSession(verifyRet.session).toJson() - val releaseRet = post(releasePath, data) - assertEquals(StateCode.Success.code, releaseRet.code) - assertEquals(0, session.getRefCount()) - - // session 被回收 - assertTrue(session.isClosed) - assertFalse(session.isActive) - } -} \ No newline at end of file diff --git a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/util/session.kt b/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/util/session.kt deleted file mode 100644 index cedc28d5..00000000 --- a/mirai-api-http/src/test/kotlin/net/mamoe/mirai/api/http/util/session.kt +++ /dev/null @@ -1,12 +0,0 @@ -package net.mamoe.mirai.api.http.util - -import net.mamoe.mirai.api.http.adapter.internal.dto.AuthedDTO -import kotlin.reflect.jvm.javaField - -internal fun T.withSession(sessionKey: String): T { - this::sessionKey.javaField?.apply { - isAccessible = true - set(this@withSession, sessionKey) - } - return this -} \ No newline at end of file