Closed LookBad closed 3 years ago
Do you know where is problem?
Hi @LookBad, could you please tell me the Ktor version and provide a reproducible snippet? I'm not sure I'm aware of the way you create the application call.
Ok. Catch up: https://gist.github.com/LookBad/89e77c6f5a45fbe386ae89115366cfe0 if you need more files, just let me know.
Body of post method in adminAuthHandler can have only val request = call.receive<AdminSignInRequest>()
to get this error.
@dmitrievanthony what's up? Did you reproduce my error?
Hi @LookBad, could you also provide the ktorTestEngine
function? I can't reproduce the issue without it.
Well, @LookBad , I definitely need to get the ktorTestEngine
method because the following code works fine:
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
object AdminAuthHandlerTest : Spek({
val engine = TestApplicationEngine(createTestEnvironment())
engine.start()
engine.application.install(Locations)
engine.application.routing {
post<Admin.Auth.SignIn> {
call.respondText("OK")
}
}
with(engine) {
describe("A sign-in endpoint") {
lateinit var call: TestApplicationCall
beforeGroup {
call = handleRequest(HttpMethod.Post, "/admin/auth/sign-in") {
addHeader(HttpHeaders.Accept, ContentType.Text.Plain.contentType)
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.contentType)
setBody(jsonAsString(AdminSignInRequest("admin", "admin123")))
}
}
it("should return 200") {
assertThat(call.response.status()).isEqualTo(HttpStatusCode.OK)
}
}
}
})
Ok
fun ktorTestEngine() =
TestApplicationEngine(createTestEnvironment()).apply {
start(wait = false)
application.parkkometrModule(true)
}
It looks like parkkometrModule
is also some extension function, isn't it?
It'is my application module.
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
@Suppress("unused", "UNUSED_PARAMETER")
fun Application.parkkometrModule(testing: Boolean = false) {
install(AutoHeadResponse)
install(Locations)
install(CORS) {
anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
method(HttpMethod.Options)
method(HttpMethod.Put)
method(HttpMethod.Post)
method(HttpMethod.Delete)
method(HttpMethod.Patch)
header(HttpHeaders.Authorization)
allowCredentials = true
allowNonSimpleContentTypes = true
}
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
dateFormat = DateFormat.getDateInstance()
findAndRegisterModules()
}
}
install(Authentication) {
jwt {
verifier(JwtConfig.verifier)
realm = "ktor.io"
validate {
val accountType = it.payload.getClaim("type").asInt()
val accountId = it.payload.getClaim("id").asInt()
if (accountType == Authenticable.TYPE_ADMIN)
accountId.let(AdminService()::findAdminById)
else
accountId.let(UserService()::findUserById)
}
}
}
install(StatusPages) {
exceptionHandler(this)
}
connectWithDatabase()
routing {
route("api/") {
adminAuthHandler()
userAuthHandler()
authenticate {
adminManageHandler()
profileHandler()
parkingHandler() }
}
}
}
I replaced gson with Jackson.
Well, @LookBad, I used your code and commented the lines I have no source to resolve. The result is here:
package test
import com.fasterxml.jackson.databind.*
import com.google.gson.*
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.auth.jwt.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.jackson.*
import io.ktor.locations.*
import io.ktor.routing.*
import io.ktor.server.testing.*
import io.ktor.util.*
import org.assertj.core.api.Assertions.assertThat
import org.spekframework.spek2.*
import org.spekframework.spek2.style.specification.*
import java.text.*
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
object AdminAuthHandlerTest : Spek({
with(ktorTestEngine()) {
describe("A sign-in endpoint") {
lateinit var call: TestApplicationCall
beforeGroup {
call = handleRequest(HttpMethod.Post, "/api/admin/auth/sign-in") {
addHeader(HttpHeaders.Accept, ContentType.Text.Plain.contentType)
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.contentType)
setBody(jsonAsString(AdminSignInRequest("admin", "admin123")))
}
}
it("should return 200") {
println("${call.request.bodyChannel} ${call.requestHandled}")
assertThat(call.response.status()).isEqualTo(HttpStatusCode.OK)
}
}
}
})
fun ktorTestEngine() =
TestApplicationEngine(createTestEnvironment()).apply {
start(wait = false)
application.parkkometrModule(true)
}
fun jsonAsString(any: Any): String {
return Gson().toJson(any)
}
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
@Suppress("unused", "UNUSED_PARAMETER")
fun Application.parkkometrModule(testing: Boolean = false) {
install(AutoHeadResponse)
install(Locations)
install(CORS) {
anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
method(HttpMethod.Options)
method(HttpMethod.Put)
method(HttpMethod.Post)
method(HttpMethod.Delete)
method(HttpMethod.Patch)
header(HttpHeaders.Authorization)
allowCredentials = true
allowNonSimpleContentTypes = true
}
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
dateFormat = DateFormat.getDateInstance()
findAndRegisterModules()
}
}
install(Authentication) {
jwt {
// verifier(JwtConfig.verifier)
realm = "ktor.io"
// validate {
// val accountType = it.payload.getClaim("type").asInt()
// val accountId = it.payload.getClaim("id").asInt()
// if (accountType == Authenticable.TYPE_ADMIN)
// accountId.let(AdminService()::findAdminById)
// else
// accountId.let(UserService()::findUserById)
// }
}
}
install(StatusPages) {
// exceptionHandler(this)
}
// connectWithDatabase()
routing {
route("api/") {
adminAuthHandler()
// userAuthHandler()
authenticate {
// adminManageHandler()
// profileHandler()
// parkingHandler()
}
}
}
}
And it works perfectly well. I'm sure we are on the right way to solve the issue, but to speed the process up could you provide the minimal, complete and reproducible examples that demonstrates the issue?
My app: Archive.zip. Ps. I know, I have to clean up the project.
What's up?
ping :)
I have a better message error after I have changed the JRE:
Expecting:
<400 Bad Request>
to be equal to:
<200 OK>
but was not.
org.opentest4j.AssertionFailedError:
Expecting:
<400 Bad Request>
to be equal to:
<200 OK>
but was not.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at com.easythings.parkkometr.test.handler.api.AdminAuthHandlerTest$1$1$1$2.invoke(AdminAuthHandlerTest.kt:36)
at com.easythings.parkkometr.test.handler.api.AdminAuthHandlerTest$1$1$1$2.invoke(AdminAuthHandlerTest.kt:23)
at org.spekframework.spek2.runtime.scope.TestScopeImpl.execute(scopes.kt:136)
at org.spekframework.spek2.runtime.Executor$execute$result$2$exception$1$job$1.invokeSuspend(Executor.kt:74)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
@dmitrievanthony what do you think about it?
@LookBad the root cause is BadContentTypeFormatException
that is thrown, because of how you add Content-Type
header in the app/test/src/handler/api/AdminAuthHandlerTest.kt
:
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.contentType)
contentType
is a property of ContentType
class that represents the first part of the media type — in your case application
(from application/json
).
To fix this you can replace property access with toString()
method call:
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
Wow! Thank you! I love it. It should be better documented.
Hi!
I wrote a test:
My test fails. In response, I always get 400 code, but I provide correct data in setBody method. When I do the request from Postman (or my client app) with the same data it works correctly.
I cannot find any information about a similar situation in docs. Do you have any idea what do I wrong?