test(api/rest): add captcha tests
This commit is contained in:
parent
46218aa3c7
commit
053b24a614
2 changed files with 68 additions and 3 deletions
|
|
@ -0,0 +1,30 @@
|
|||
package moe.lava.neon.tests.api
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import moe.lava.neon.api.ApiClient
|
||||
import moe.lava.neon.api.endpoints.getExperiments
|
||||
import moe.lava.neon.common.captcha.CaptchaResponse
|
||||
|
||||
class CaptchaTest : FunSpec({
|
||||
val mock = DiscordApiMock()
|
||||
val client = ApiClient(mock.engine, false)
|
||||
|
||||
val (captchaReq, captchaRes) = mock.generateCaptcha()
|
||||
mock.isCaptchaEnabled = true
|
||||
|
||||
test("captcha should not be handled") {
|
||||
val res = client.getExperiments().response
|
||||
res.status.value shouldBe 400
|
||||
}
|
||||
|
||||
test("captcha should be handled") {
|
||||
client.setCaptchaHandler { req ->
|
||||
captchaRes
|
||||
.takeIf { req == captchaReq }
|
||||
?: CaptchaResponse.Failed(Throwable())
|
||||
}
|
||||
val res = client.getExperiments().response
|
||||
res.status.value shouldBe 200
|
||||
}
|
||||
})
|
||||
|
|
@ -15,11 +15,19 @@ import io.ktor.http.HttpHeaders
|
|||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.content.TextContent
|
||||
import io.ktor.http.headersOf
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonNamingStrategy
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import moe.lava.neon.common.captcha.CaptchaRequest
|
||||
import moe.lava.neon.common.captcha.CaptchaResponse
|
||||
import moe.lava.neon.tests.api.mock.AuthResponse
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
private val JsonWithSnakecase = Json {
|
||||
namingStrategy = JsonNamingStrategy.SnakeCase
|
||||
}
|
||||
private val JsonHeader = headersOf(HttpHeaders.ContentType, "application/json")
|
||||
|
||||
val idArb = Arb.long(1e18.toLong(), 1e20.toLong())
|
||||
|
|
@ -29,21 +37,44 @@ val tokenArb = Arb.stringPattern("(mfa\\.[a-zA-Z0-9_-]{20,})|([a-zA-Z0-9_-]{23,2
|
|||
class DiscordApiMock {
|
||||
private val fingerprints = mutableListOf<String>()
|
||||
private val logins = mutableMapOf<String, String>()
|
||||
private var captcha: Pair<CaptchaRequest, CaptchaResponse.Success> = generateCaptcha()
|
||||
|
||||
var isCaptchaEnabled = false
|
||||
|
||||
fun createLogin(email: String, password: String) {
|
||||
logins[email] = password
|
||||
}
|
||||
|
||||
fun generateCaptcha(): Pair<CaptchaRequest, CaptchaResponse.Success> {
|
||||
val req = CaptchaRequest(
|
||||
listOf(Arb.string().single()),
|
||||
Arb.string().single(),
|
||||
Arb.string().single(),
|
||||
Arb.string().single(),
|
||||
Arb.string().single(),
|
||||
Arb.string().single(),
|
||||
true,
|
||||
)
|
||||
val res = CaptchaResponse.Success(Arb.string().single())
|
||||
captcha = req to res
|
||||
return req to res
|
||||
}
|
||||
|
||||
val engine = MockEngine { req ->
|
||||
if (!req.url.toString().startsWith("https://discord.com/api/v9")) {
|
||||
return@MockEngine respondError(HttpStatusCode.NotFound)
|
||||
}
|
||||
if (isCaptchaEnabled) {
|
||||
if (req.headers["X-Captcha-Key"] != captcha.second.token) {
|
||||
return@MockEngine respondJson(JsonWithSnakecase.encodeToString(captcha.first), HttpStatusCode.BadRequest)
|
||||
}
|
||||
}
|
||||
val path = req.url.encodedPath.replaceFirst("/api/v9", "")
|
||||
return@MockEngine when (path) {
|
||||
"/experiments" -> {
|
||||
val fp = Arb.string(18..20, "123456789").single()
|
||||
fingerprints.add(fp)
|
||||
respond(AuthResponse.Experiments(fp), headers = JsonHeader)
|
||||
respondJson(AuthResponse.Experiments(fp))
|
||||
}
|
||||
"/auth/login" -> {
|
||||
val body = req.body as? TextContent
|
||||
|
|
@ -58,13 +89,17 @@ class DiscordApiMock {
|
|||
return@MockEngine badReq("Unknown credentials")
|
||||
}
|
||||
|
||||
respond(AuthResponse.Login(idArb.next(), tokenArb.next()), headers = JsonHeader)
|
||||
respondJson(AuthResponse.Login(idArb.next(), tokenArb.next()))
|
||||
}
|
||||
else -> respondError(HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun MockRequestHandleScope.badReq(msg: String): HttpResponseData =
|
||||
private inline fun MockRequestHandleScope.badReq(msg: String): HttpResponseData =
|
||||
respondError(HttpStatusCode.BadRequest, content = "[Neon] $msg")
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun MockRequestHandleScope.respondJson(content: String, status: HttpStatusCode = HttpStatusCode.OK): HttpResponseData =
|
||||
respond(content = content, status = status, headers = JsonHeader)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue