Skip to content

Commit

Permalink
Migrate unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
ryoii committed Sep 28, 2023
1 parent 91ec390 commit 0391406
Show file tree
Hide file tree
Showing 19 changed files with 472 additions and 853 deletions.
1 change: 1 addition & 0 deletions mirai-api-http/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
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.*
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
Expand Down Expand Up @@ -52,7 +56,7 @@ class MahApplicationTestBuilder(private val builder: ApplicationTestBuilder): Cl
}

fun installWebHookAdapter() {
// TODO
buildAdapter<WebhookAdapter>("webhook").enable()
}

private inline fun <reified T : MahAdapter> buildAdapter(adapter: String): T {
Expand All @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
168 changes: 168 additions & 0 deletions mirai-api-http/src/test/kotlin/integration/auth/HttpAuthTest.kt
Original file line number Diff line number Diff line change
@@ -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<StateCode>(pathsVerify, VerifyDTO("wrong $verifyKey")).also {
assertEquals(StateCode.AuthKeyFail.code, it.code)
}

postJsonData<VerifyRetDTO>(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<VerifyRetDTO>(pathsVerify, VerifyDTO(verifyKey)).also {
assertEquals(StateCode.Success.code, it.code)
}

postJsonData<VerifyRetDTO>(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<StateCode>(pathsVerify, VerifyDTO("wrong $verifyKey")).also {
assertEquals(StateCode.AuthKeyFail.code, it.code)
}

val verifyRet = postJsonData<VerifyRetDTO>(pathsVerify, VerifyDTO(verifyKey)).also {
assertEquals(StateCode.Success.code, it.code)
assertNotNull(it.session)
}

// 认证但未绑定
getSessionInfoAndExpect(verifyRet.session, errorState = StateCode.NotVerifySession)

// bind
postJsonData<StateCode>(pathsBind, BindDTO(SetupMockBot.ID)).also {
assertEquals(StateCode.IllegalSession.code, it.code)
}

// bind with session
postJsonData<StateCode>(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<VerifyRetDTO>(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<StateCode>(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<ElementResult>()

val ret = resp.data.jsonElementParseOrNull<SessionDTO>()

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<StateCode>()

assertNotNull(ret)
assertEquals(errorState.code, ret.code)
}
}
}
146 changes: 146 additions & 0 deletions mirai-api-http/src/test/kotlin/integration/auth/WebSocketAuthTest.kt
Original file line number Diff line number Diff line change
@@ -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<StateCode>().also {
assertEquals(StateCode.AuthKeyFail.code, it.code)
}
}

client.ws("/all?verifyKey=$verifyKey") {
receiveDTO<VerifyRetDTO>().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<VerifyRetDTO>().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<StateCode>().also {
assertEquals(StateCode.AuthKeyFail.code, it.code)
}
}

// 不绑定账号
client.ws("/all?verifyKey=$verifyKey") {
receiveDTO<StateCode>().also {
assertEquals(StateCode.InvalidParameter.code, it.code)
}
}

// 无法绑定账号(绑定错误账号)
client.ws("/all?verifyKey=$verifyKey&qq=${SetupMockBot.ID + 1}") {
receiveDTO<StateCode>().also {
assertEquals(StateCode.NoBot.code, it.code)
}
}

// 通过已有 session 绑定

// 通过 http 创建一个session
val session = postJsonData<VerifyRetDTO>(verifyPath, VerifyDTO(verifyKey)).session

// 通过 ws 绑定错误 session
client.ws("/all?verifyKey=$verifyKey&sessionKey=wrong$session") {
receiveDTO<StateCode>().also {
assertEquals(StateCode.IllegalSession.code, it.code)
}
}

// 通过 ws 绑定已有未认证 session
client.ws("/all?verifyKey=$verifyKey&sessionKey=$session") {
receiveDTO<StateCode>().also {
assertEquals(StateCode.NotVerifySession.code, it.code)
}
}

// 通过 http 认证 session
postJsonData<StateCode>(bindPath, BindDTO(SetupMockBot.ID).withSession(session))

var ret: VerifyRetDTO? = null
// 通过 ws 绑定已有已认证 session
client.ws("/all?verifyKey=$verifyKey&sessionKey=$session") {
receiveDTO<VerifyRetDTO>().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<VerifyRetDTO>().also {
assertEquals(StateCode.Success.code, it.code)
assertNotEquals(MahContext.SINGLE_SESSION_KEY, it.session)
// not same session
assertNotEquals(ret?.session, it.session)
}
}
}
}
Loading

0 comments on commit 0391406

Please sign in to comment.