From 9879ff48d6a0ec5a040a978b7b28f17a9afcaa76 Mon Sep 17 00:00:00 2001 From: Sergey Shumov Date: Wed, 30 Oct 2024 19:51:16 +0300 Subject: [PATCH] support ktor 3 ktor 3.0.1 kotlin 2.0.21 --- detekt.yml | 6 + gradle/libs.versions.toml | 12 +- .../openapigen/content/type/BodyParser.kt | 5 +- .../content/type/ContentTypeResponder.kt | 7 +- .../content/type/ResponseSerializer.kt | 7 +- .../type/binary/BinaryContentTypeParser.kt | 24 ++- .../content/type/ktor/KtorContentProvider.kt | 13 +- .../MultipartFormDataContentProvider.kt | 32 +++- .../modules/providers/AuthProvider.kt | 5 +- .../ktor/openapigen/route/Functions.kt | 4 +- .../ktor/openapigen/route/OpenAPIRoute.kt | 13 +- .../ktor/openapigen/route/RouteConfig.kt | 3 +- .../papsign/ktor/openapigen/route/Throws.kt | 5 +- .../openapigen/route/path/auth/Functions.kt | 2 +- .../openapigen/route/path/normal/Functions.kt | 2 +- .../OpenAPIPipelineResponseContext.kt | 13 +- .../route/util/RouteBuildingUtil.kt | 4 +- .../provider/DefaultObjectSchemaProvider.kt | 14 +- .../DefaultPrimitiveSchemaProvider.kt | 8 +- .../JwtAuthDocumentationGenerationTest.kt | 42 +++-- src/test/kotlin/OneOf.kt | 41 +++-- src/test/kotlin/TestServer.kt | 6 +- src/test/kotlin/TestServerWithJwtAuth.kt | 10 +- .../openapigen/EnumNonStrictTestServer.kt | 156 +++++++++------- .../ktor/openapigen/EnumStrictTestServer.kt | 173 +++++++++--------- .../FormDocumentationGenerationTest.kt | 100 +++++----- .../binary/BinaryContentTypeParserTest.kt | 85 +++++---- .../MultipartFormDataContentProviderTest.kt | 63 +++++-- .../openapigen/routing/GenericRoutesTest.kt | 144 +++++++++------ .../ktor/openapigen/routing/GenericsTest.kt | 90 +++++---- .../ktor/openapigen/routing/RoutingTest.kt | 137 +++++++------- 31 files changed, 686 insertions(+), 540 deletions(-) create mode 100644 detekt.yml diff --git a/detekt.yml b/detekt.yml new file mode 100644 index 000000000..b5c3bee87 --- /dev/null +++ b/detekt.yml @@ -0,0 +1,6 @@ +style: + MaxLineLength: + active: false +naming: + MemberNameEqualsClassName: + active: false \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0c2083d6c..3c58be6da 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,17 @@ [versions] slf4j = "2.0.12" -logback = "1.4.12" -ktor = "2.3.11" -junit = "5.10.2" -kotlin = "1.8.22" -dokka = "1.8.20" +logback = "1.5.10" +ktor = "3.0.1" +junit = "5.11.3" +kotlin = "2.0.21" +dokka = "1.9.20" # we can not switch to 3.x.x because we want to keep it compatible with JVM 8 nemerosaVersioning = "2.15.1" nexusPublish = "1.1.0" # when updating version here, don't forge to update version in OpenAPIGen.kt line 68 swaggerUi = "5.17.2" reflections = "0.10.2" -jackson = "2.17.1" +jackson = "2.18.1" axion = "1.15.1" [libraries] diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/BodyParser.kt b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/BodyParser.kt index 6f1fa21bf..6a718b7fd 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/BodyParser.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/BodyParser.kt @@ -1,11 +1,10 @@ package com.papsign.ktor.openapigen.content.type import io.ktor.http.ContentType -import io.ktor.server.application.ApplicationCall -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext import kotlin.reflect.KType interface BodyParser: ContentTypeProvider { fun getParseableContentTypes(type: KType): List - suspend fun parseBody(clazz: KType, request: PipelineContext): T + suspend fun parseBody(clazz: KType, request: RoutingContext): T } diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ContentTypeResponder.kt b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ContentTypeResponder.kt index c2889586d..3ead5667e 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ContentTypeResponder.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ContentTypeResponder.kt @@ -3,15 +3,14 @@ package com.papsign.ktor.openapigen.content.type import com.papsign.ktor.openapigen.route.response.Responder import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext data class ContentTypeResponder(val responseSerializer: ResponseSerializer, val contentType: ContentType): Responder { - override suspend fun respond(response: T, request: PipelineContext) { + override suspend fun respond(response: T, request: RoutingContext) { responseSerializer.respond(response, request, contentType) } - override suspend fun respond(statusCode: HttpStatusCode, response: T, request: PipelineContext) { + override suspend fun respond(statusCode: HttpStatusCode, response: T, request: RoutingContext) { responseSerializer.respond(statusCode, response, request, contentType) } } diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ResponseSerializer.kt b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ResponseSerializer.kt index 4a61a9c14..d2d9747eb 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ResponseSerializer.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ResponseSerializer.kt @@ -2,8 +2,7 @@ package com.papsign.ktor.openapigen.content.type import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext import kotlin.reflect.KType interface ResponseSerializer: ContentTypeProvider { @@ -11,6 +10,6 @@ interface ResponseSerializer: ContentTypeProvider { * used to determine which registered response serializer is used, based on the accept header */ fun getSerializableContentTypes(type: KType): List - suspend fun respond(response: T, request: PipelineContext, contentType: ContentType) - suspend fun respond(statusCode: HttpStatusCode, response: T, request: PipelineContext, contentType: ContentType) + suspend fun respond(response: T, request: RoutingContext, contentType: ContentType) + suspend fun respond(statusCode: HttpStatusCode, response: T, request: RoutingContext, contentType: ContentType) } diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryContentTypeParser.kt b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryContentTypeParser.kt index c8337ab15..20e6fe74c 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryContentTypeParser.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryContentTypeParser.kt @@ -1,24 +1,30 @@ package com.papsign.ktor.openapigen.content.type.binary -import com.papsign.ktor.openapigen.* +import com.papsign.ktor.openapigen.OpenAPIGen +import com.papsign.ktor.openapigen.OpenAPIGenModuleExtension import com.papsign.ktor.openapigen.annotations.Response import com.papsign.ktor.openapigen.content.type.BodyParser import com.papsign.ktor.openapigen.content.type.ContentTypeProvider import com.papsign.ktor.openapigen.content.type.ResponseSerializer import com.papsign.ktor.openapigen.exceptions.assertContent +import com.papsign.ktor.openapigen.getKType import com.papsign.ktor.openapigen.model.operation.MediaTypeModel import com.papsign.ktor.openapigen.model.schema.DataFormat import com.papsign.ktor.openapigen.model.schema.DataType import com.papsign.ktor.openapigen.model.schema.SchemaModel import com.papsign.ktor.openapigen.modules.ModuleProvider +import com.papsign.ktor.openapigen.unitKType import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall import io.ktor.server.request.receiveStream import io.ktor.server.response.respondBytes -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext import java.io.InputStream -import kotlin.reflect.* +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KProperty1 +import kotlin.reflect.KType +import kotlin.reflect.KVisibility import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.findAnnotation import kotlin.reflect.jvm.jvmErasure @@ -37,21 +43,21 @@ object BinaryContentTypeParser: BodyParser, ResponseSerializer, OpenAPIGenModule return type.jvmErasure.findAnnotation()?.contentTypes?.map(ContentType.Companion::parse) ?: listOf() } - override suspend fun respond(response: T, request: PipelineContext, contentType: ContentType) { + override suspend fun respond(response: T, request: RoutingContext, contentType: ContentType) { val code = response::class.findAnnotation()?.statusCode?.let { HttpStatusCode.fromValue(it) } ?: HttpStatusCode.OK respond(code, response, request, contentType) } - override suspend fun respond(statusCode: HttpStatusCode, response: T, request: PipelineContext, contentType: ContentType) { + override suspend fun respond(statusCode: HttpStatusCode, response: T, request: RoutingContext, contentType: ContentType) { @Suppress("UNCHECKED_CAST") val prop = response::class.declaredMemberProperties.first { it.visibility == KVisibility.PUBLIC } as KProperty1 val data = prop.get(response) as InputStream - request.context.respondBytes(data.readBytes(), contentType, statusCode) + request.call.respondBytes(data.readBytes(), contentType, statusCode) } @Suppress("UNCHECKED_CAST") - override suspend fun parseBody(clazz: KType, request: PipelineContext): T { - return (clazz.classifier as KClass).getAcceptableConstructor().call( request.context.receiveStream()) + override suspend fun parseBody(clazz: KType, request: RoutingContext): T { + return (clazz.classifier as KClass).getAcceptableConstructor().call( request.call.receiveStream()) } override fun getMediaType(type: KType, apiGen: OpenAPIGen, provider: ModuleProvider<*>, example: T?, usage: ContentTypeProvider.Usage): Map>? { diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ktor/KtorContentProvider.kt b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ktor/KtorContentProvider.kt index 53d6764f0..4691f41b6 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ktor/KtorContentProvider.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/ktor/KtorContentProvider.kt @@ -15,18 +15,15 @@ import com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderPro import com.papsign.ktor.openapigen.unitKType import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall import io.ktor.server.application.PluginBuilder import io.ktor.server.application.PluginInstance -import io.ktor.server.application.call import io.ktor.server.application.pluginOrNull import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.plugins.contentnegotiation.ContentNegotiationConfig import io.ktor.server.request.receive import io.ktor.server.response.respond -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext import io.ktor.util.reflect.TypeInfo -import io.ktor.util.reflect.platformType import kotlin.reflect.KProperty1 import kotlin.reflect.KType import kotlin.reflect.full.findAnnotation @@ -111,10 +108,10 @@ object KtorContentProvider : ContentTypeProvider, BodyParser, ResponseSerializer return contentTypes!!.toList() } - override suspend fun parseBody(clazz: KType, request: PipelineContext): T { + override suspend fun parseBody(clazz: KType, request: RoutingContext): T { val info = TypeInfo( type = clazz.jvmErasure, - reifiedType = clazz.platformType, +// reifiedType = clazz.platformType, kotlinType = clazz ) return request.call.receive(info) @@ -126,7 +123,7 @@ object KtorContentProvider : ContentTypeProvider, BodyParser, ResponseSerializer override suspend fun respond( response: T, - request: PipelineContext, + request: RoutingContext, contentType: ContentType ) { request.call.respond(response as Any) @@ -135,7 +132,7 @@ object KtorContentProvider : ContentTypeProvider, BodyParser, ResponseSerializer override suspend fun respond( statusCode: HttpStatusCode, response: T, - request: PipelineContext, + request: RoutingContext, contentType: ContentType ) { request.call.respond(statusCode, response as Any) diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProvider.kt b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProvider.kt index e093e334c..34e3729c1 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProvider.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProvider.kt @@ -1,11 +1,14 @@ package com.papsign.ktor.openapigen.content.type.multipart -import com.papsign.ktor.openapigen.* +import com.papsign.ktor.openapigen.OpenAPIGen +import com.papsign.ktor.openapigen.OpenAPIGenModuleExtension import com.papsign.ktor.openapigen.annotations.mapping.openAPIName import com.papsign.ktor.openapigen.content.type.BodyParser import com.papsign.ktor.openapigen.content.type.ContentTypeProvider import com.papsign.ktor.openapigen.exceptions.OpenAPIParseException import com.papsign.ktor.openapigen.exceptions.assertContent +import com.papsign.ktor.openapigen.getKType +import com.papsign.ktor.openapigen.isValue import com.papsign.ktor.openapigen.model.operation.MediaTypeEncodingModel import com.papsign.ktor.openapigen.model.operation.MediaTypeModel import com.papsign.ktor.openapigen.model.schema.SchemaModel @@ -15,14 +18,23 @@ import com.papsign.ktor.openapigen.parameters.util.localDateTimeFormatter import com.papsign.ktor.openapigen.parameters.util.offsetDateTimeFormatter import com.papsign.ktor.openapigen.parameters.util.zonedDateTimeFormatter import com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProviderModule -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.server.application.* -import io.ktor.server.request.* -import io.ktor.util.* -import io.ktor.util.pipeline.* +import com.papsign.ktor.openapigen.unitKType +import com.papsign.ktor.openapigen.unwrappedType +import io.ktor.http.ContentType +import io.ktor.http.content.PartData +import io.ktor.http.content.forEachPart +import io.ktor.http.content.streamProvider +import io.ktor.server.request.receiveMultipart +import io.ktor.server.routing.RoutingContext +import io.ktor.util.asStream import java.io.InputStream -import java.time.* +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime +import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.util.* import kotlin.collections.set @@ -105,9 +117,9 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension private val typeContentTypes = HashMap>() - override suspend fun parseBody(clazz: KType, request: PipelineContext): T { + override suspend fun parseBody(clazz: KType, request: RoutingContext): T { val objectMap = HashMap() - request.context.receiveMultipart().forEachPart { + request.call.receiveMultipart().forEachPart { val name = it.name if (name != null) { when (it) { diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/AuthProvider.kt b/src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/AuthProvider.kt index e7fcec49b..e43c8fdf2 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/AuthProvider.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/AuthProvider.kt @@ -8,12 +8,11 @@ import com.papsign.ktor.openapigen.modules.OpenAPIModule import com.papsign.ktor.openapigen.modules.handlers.AuthHandler import com.papsign.ktor.openapigen.route.path.auth.OpenAPIAuthenticatedRoute import com.papsign.ktor.openapigen.route.path.normal.NormalOpenAPIRoute -import io.ktor.server.application.ApplicationCall -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext import kotlin.reflect.KType interface AuthProvider: OpenAPIModule, DependentModule { - suspend fun getAuth(pipeline: PipelineContext): TAuth + suspend fun getAuth(pipeline: RoutingContext): TAuth fun apply(route: NormalOpenAPIRoute): OpenAPIAuthenticatedRoute val security: Iterable>> override val handlers: Collection> diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/route/Functions.kt b/src/main/kotlin/com/papsign/ktor/openapigen/route/Functions.kt index 273481ce0..df44603bd 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/route/Functions.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/route/Functions.kt @@ -3,7 +3,6 @@ package com.papsign.ktor.openapigen.route import com.papsign.ktor.openapigen.APITag import com.papsign.ktor.openapigen.annotations.Path import com.papsign.ktor.openapigen.content.type.ContentTypeProvider -import com.papsign.ktor.openapigen.getKType import com.papsign.ktor.openapigen.modules.handlers.RequestHandlerModule import com.papsign.ktor.openapigen.modules.handlers.ResponseHandlerModule import com.papsign.ktor.openapigen.modules.registerModule @@ -12,13 +11,12 @@ import com.papsign.ktor.openapigen.route.modules.PathProviderModule import io.ktor.http.HttpMethod import io.ktor.server.routing.HttpMethodRouteSelector import io.ktor.server.routing.createRouteFromPath -import io.ktor.util.KtorDsl +import io.ktor.utils.io.KtorDsl import kotlin.reflect.KType import kotlin.reflect.KTypeProjection import kotlin.reflect.KVariance import kotlin.reflect.full.createType import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.starProjectedType import kotlin.reflect.jvm.jvmErasure import kotlin.reflect.typeOf diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/route/OpenAPIRoute.kt b/src/main/kotlin/com/papsign/ktor/openapigen/route/OpenAPIRoute.kt index 387f538e2..888471e87 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/route/OpenAPIRoute.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/route/OpenAPIRoute.kt @@ -1,7 +1,11 @@ package com.papsign.ktor.openapigen.route import com.papsign.ktor.openapigen.classLogger -import com.papsign.ktor.openapigen.content.type.* +import com.papsign.ktor.openapigen.content.type.BodyParser +import com.papsign.ktor.openapigen.content.type.ContentTypeResponder +import com.papsign.ktor.openapigen.content.type.ResponseSerializer +import com.papsign.ktor.openapigen.content.type.SelectedParser +import com.papsign.ktor.openapigen.content.type.SelectedSerializer import com.papsign.ktor.openapigen.content.type.ktor.KtorContentProvider import com.papsign.ktor.openapigen.exceptions.OpenAPINoParserException import com.papsign.ktor.openapigen.exceptions.OpenAPINoSerializerException @@ -15,15 +19,12 @@ import com.papsign.ktor.openapigen.parameters.util.buildParameterHandler import com.papsign.ktor.openapigen.route.response.Responder import com.papsign.ktor.openapigen.validation.ValidationHandler import io.ktor.http.ContentType -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call import io.ktor.server.request.contentType import io.ktor.server.routing.Route +import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.accept import io.ktor.server.routing.application import io.ktor.server.routing.contentType -import io.ktor.util.pipeline.PipelineContext -import io.ktor.util.reflect.TypeInfo import kotlin.reflect.KType import kotlin.reflect.KTypeProjection import kotlin.reflect.KVariance @@ -38,7 +39,7 @@ abstract class OpenAPIRoute>(val ktorRoute: Route, val provi paramsType: KType, responseType: KType, bodyType: KType, - pass: suspend OpenAPIRoute<*>.(pipeline: PipelineContext, responder: Responder, P, B) -> Unit + pass: suspend OpenAPIRoute<*>.(pipeline: RoutingContext, responder: Responder, P, B) -> Unit ) { val parameterHandler = buildParameterHandler

(paramsType) provider.registerModule(parameterHandler, ParameterHandler::class.createType(listOf(KTypeProjection(KVariance.INVARIANT, paramsType)))) diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/route/RouteConfig.kt b/src/main/kotlin/com/papsign/ktor/openapigen/route/RouteConfig.kt index c087c2051..0ed194ccd 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/route/RouteConfig.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/route/RouteConfig.kt @@ -5,8 +5,9 @@ import com.papsign.ktor.openapigen.route.path.normal.NormalOpenAPIRoute import io.ktor.server.application.Application import io.ktor.server.application.plugin import io.ktor.server.routing.Routing +import io.ktor.server.routing.application import io.ktor.server.routing.routing -import io.ktor.util.KtorDsl +import io.ktor.utils.io.KtorDsl /** * Wrapper for [io.ktor.routing.routing] to create the endpoints while configuring OpenAPI diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/route/Throws.kt b/src/main/kotlin/com/papsign/ktor/openapigen/route/Throws.kt index b2be03fe4..2f994e04b 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/route/Throws.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/route/Throws.kt @@ -5,10 +5,11 @@ import com.papsign.ktor.openapigen.modules.providers.ThrowInfoProvider import com.papsign.ktor.openapigen.modules.registerModule import com.papsign.ktor.openapigen.route.util.createConstantChild import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationCallPipeline +import io.ktor.server.application.PipelineCall import io.ktor.server.application.call import io.ktor.server.response.respond +import io.ktor.server.routing.intercept import io.ktor.util.pipeline.PipelineContext import kotlinx.coroutines.coroutineScope import kotlin.reflect.KClass @@ -60,7 +61,7 @@ inline fun > T.throws(vararg responses: APIException<*, *>, c } } -fun makeExceptionHandler(info: Array>): suspend PipelineContext<*, ApplicationCall>.(t: Throwable) -> Unit { +fun makeExceptionHandler(info: Array>): suspend PipelineContext<*, PipelineCall>.(t: Throwable) -> Unit { val classes = info.associateBy { it.exceptionClass } fun findHandlerByType(clazz: KClass<*>): APIException<*, *>? { classes[clazz]?.let { return it } diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/route/path/auth/Functions.kt b/src/main/kotlin/com/papsign/ktor/openapigen/route/path/auth/Functions.kt index aa0350dc7..d53127c7f 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/route/path/auth/Functions.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/route/path/auth/Functions.kt @@ -5,7 +5,7 @@ import com.papsign.ktor.openapigen.route.method import com.papsign.ktor.openapigen.route.preHandle import com.papsign.ktor.openapigen.route.response.OpenAPIPipelineAuthContext import io.ktor.http.HttpMethod -import io.ktor.util.KtorDsl +import io.ktor.utils.io.KtorDsl import kotlin.reflect.full.starProjectedType import kotlin.reflect.typeOf diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/route/path/normal/Functions.kt b/src/main/kotlin/com/papsign/ktor/openapigen/route/path/normal/Functions.kt index 6e16d52dc..1816f9ad1 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/route/path/normal/Functions.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/route/path/normal/Functions.kt @@ -5,7 +5,7 @@ import com.papsign.ktor.openapigen.route.method import com.papsign.ktor.openapigen.route.preHandle import com.papsign.ktor.openapigen.route.response.OpenAPIPipelineResponseContext import io.ktor.http.HttpMethod -import io.ktor.util.KtorDsl +import io.ktor.utils.io.KtorDsl import kotlin.reflect.full.starProjectedType import kotlin.reflect.typeOf diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/route/response/OpenAPIPipelineResponseContext.kt b/src/main/kotlin/com/papsign/ktor/openapigen/route/response/OpenAPIPipelineResponseContext.kt index 7b6a46052..40579c380 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/route/response/OpenAPIPipelineResponseContext.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/route/response/OpenAPIPipelineResponseContext.kt @@ -7,18 +7,17 @@ import com.papsign.ktor.openapigen.modules.providers.AuthProvider import com.papsign.ktor.openapigen.modules.providers.StatusProvider import com.papsign.ktor.openapigen.route.OpenAPIRoute import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext import kotlin.reflect.full.findAnnotation interface Responder { - suspend fun respond(response: TResponse, request: PipelineContext) - suspend fun respond(statusCode: HttpStatusCode, response: TResponse, request: PipelineContext) + suspend fun respond(response: TResponse, request: RoutingContext) + suspend fun respond(statusCode: HttpStatusCode, response: TResponse, request: RoutingContext) } interface OpenAPIPipelineContext { val route: OpenAPIRoute<*> - val pipeline: PipelineContext + val pipeline: RoutingContext val responder: Responder } @@ -28,13 +27,13 @@ interface OpenAPIPipelineAuthContext : OpenAPIPipelineResponse } class ResponseContextImpl( - override val pipeline: PipelineContext, + override val pipeline: RoutingContext, override val route: OpenAPIRoute<*>, override val responder: Responder ) : OpenAPIPipelineResponseContext class AuthResponseContextImpl( - override val pipeline: PipelineContext, + override val pipeline: RoutingContext, override val authProvider: AuthProvider, override val route: OpenAPIRoute<*>, override val responder: Responder diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/route/util/RouteBuildingUtil.kt b/src/main/kotlin/com/papsign/ktor/openapigen/route/util/RouteBuildingUtil.kt index 89ea0db7f..e169acbf6 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/route/util/RouteBuildingUtil.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/route/util/RouteBuildingUtil.kt @@ -10,8 +10,8 @@ fun Route.createConstantChild(): Route { return createChild(ConstantRouteSelector()) } -class ConstantRouteSelector: RouteSelector(RouteSelectorEvaluation.qualityConstant) { - override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { +class ConstantRouteSelector: RouteSelector() { + override suspend fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { return RouteSelectorEvaluation.Constant } } diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultObjectSchemaProvider.kt b/src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultObjectSchemaProvider.kt index b924a0c08..081564633 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultObjectSchemaProvider.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultObjectSchemaProvider.kt @@ -1,7 +1,10 @@ package com.papsign.ktor.openapigen.schema.builder.provider -import com.papsign.ktor.openapigen.* +import com.papsign.ktor.openapigen.OpenAPIGen +import com.papsign.ktor.openapigen.OpenAPIGenModuleExtension import com.papsign.ktor.openapigen.classLogger +import com.papsign.ktor.openapigen.getKType +import com.papsign.ktor.openapigen.memberProperties import com.papsign.ktor.openapigen.model.schema.SchemaModel import com.papsign.ktor.openapigen.modules.DefaultOpenAPIModule import com.papsign.ktor.openapigen.modules.ModuleProvider @@ -10,6 +13,15 @@ import com.papsign.ktor.openapigen.schema.builder.FinalSchemaBuilder import com.papsign.ktor.openapigen.schema.builder.SchemaBuilder import com.papsign.ktor.openapigen.schema.namer.DefaultSchemaNamer import com.papsign.ktor.openapigen.schema.namer.SchemaNamer +import kotlin.collections.HashMap +import kotlin.collections.List +import kotlin.collections.associate +import kotlin.collections.filter +import kotlin.collections.first +import kotlin.collections.lastOrNull +import kotlin.collections.listOf +import kotlin.collections.map +import kotlin.collections.set import kotlin.reflect.KType import kotlin.reflect.KVisibility import kotlin.reflect.full.starProjectedType diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultPrimitiveSchemaProvider.kt b/src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultPrimitiveSchemaProvider.kt index f0b13d690..ea2bde3c0 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultPrimitiveSchemaProvider.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultPrimitiveSchemaProvider.kt @@ -13,7 +13,13 @@ import com.papsign.ktor.openapigen.schema.builder.SchemaBuilder import java.io.InputStream import java.math.BigDecimal import java.math.BigInteger -import java.time.* +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime +import java.time.ZonedDateTime import java.util.* import kotlin.reflect.KType diff --git a/src/test/kotlin/JwtAuthDocumentationGenerationTest.kt b/src/test/kotlin/JwtAuthDocumentationGenerationTest.kt index 03f9cfef1..e0dcc0267 100644 --- a/src/test/kotlin/JwtAuthDocumentationGenerationTest.kt +++ b/src/test/kotlin/JwtAuthDocumentationGenerationTest.kt @@ -1,24 +1,28 @@ package origo.booking import TestServerWithJwtAuth.testServerWithJwtAuth -import io.ktor.http.* -import io.ktor.server.testing.* -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import io.ktor.server.testing.testApplication +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import kotlin.test.assertTrue internal class JwtAuthDocumentationGenerationTest { - @Test - fun testRequest() = withTestApplication({ - testServerWithJwtAuth() - }) { - with(handleRequest(HttpMethod.Get, "//openapi.json")) { - assertEquals(HttpStatusCode.OK, response.status()) - assertTrue( - response.content!!.contains( - """"securitySchemes" : { + fun testRequest() = testApplication { + application { + testServerWithJwtAuth() + } + val response = client.get("/openapi.json") + + assertEquals(HttpStatusCode.OK, response.status) + val body = response.bodyAsText() + assertTrue( + body.contains( + """"securitySchemes" : { "ThisIsSchemeName" : { "in" : "cookie", "name" : "ThisIsCookieName", @@ -30,16 +34,16 @@ internal class JwtAuthDocumentationGenerationTest { "type" : "http" } }""" - ) ) - assertTrue( - response.content!!.contains( - """"security" : [ { + ) + + assertTrue( + body.contains( + """"security" : [ { "jwtAuth" : [ ], "ThisIsSchemeName" : [ ] }""" - ) ) - } + ) } } diff --git a/src/test/kotlin/OneOf.kt b/src/test/kotlin/OneOf.kt index 8d8904884..d5ada398f 100644 --- a/src/test/kotlin/OneOf.kt +++ b/src/test/kotlin/OneOf.kt @@ -1,11 +1,11 @@ import TestServer.setupBaseTestServer import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.annotation.JsonTypeName -import com.papsign.ktor.openapigen.annotations.type.`object`.example.ExampleProvider -import com.papsign.ktor.openapigen.annotations.type.`object`.example.WithExample import com.papsign.ktor.openapigen.annotations.type.number.integer.clamp.Clamp import com.papsign.ktor.openapigen.annotations.type.number.integer.max.Max import com.papsign.ktor.openapigen.annotations.type.number.integer.min.Min +import com.papsign.ktor.openapigen.annotations.type.`object`.example.ExampleProvider +import com.papsign.ktor.openapigen.annotations.type.`object`.example.WithExample import com.papsign.ktor.openapigen.annotations.type.string.example.DiscriminatorAnnotation import com.papsign.ktor.openapigen.route.apiRouting import com.papsign.ktor.openapigen.route.info @@ -13,10 +13,13 @@ import com.papsign.ktor.openapigen.route.path.normal.NormalOpenAPIRoute import com.papsign.ktor.openapigen.route.path.normal.post import com.papsign.ktor.openapigen.route.response.respond import com.papsign.ktor.openapigen.route.route -import io.ktor.http.* -import io.ktor.server.testing.* -import org.junit.Assert +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import io.ktor.server.testing.testApplication import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue fun NormalOpenAPIRoute.SealedRoute() { route("sealed") { @@ -56,16 +59,21 @@ val ref = "\$ref" internal class OneOfLegacyGenerationTests { @Test - fun willDiscriminatorsBePresent() = withTestApplication({ - setupBaseTestServer() - apiRouting { - SealedRoute() + fun willDiscriminatorsBePresent() = testApplication { + application { + setupBaseTestServer() + apiRouting { + SealedRoute() + } } - }) { - with(handleRequest(HttpMethod.Get, "//openapi.json")) { - Assert.assertEquals(HttpStatusCode.OK, response.status()) - Assert.assertTrue( - response.content!!.contains( + + val response = client.get("/openapi.json") + assertEquals(HttpStatusCode.OK, response.status) + val body = response.bodyAsText() + + + assertTrue( + body.contains( """"Base" : { "discriminator" : { "propertyName" : "type" @@ -86,8 +94,8 @@ internal class OneOfLegacyGenerationTests { }""" ) ) - Assert.assertTrue( - response.content!!.contains( + assertTrue( + body.contains( """"A" : { "discriminator" : { "propertyName" : "type" @@ -109,6 +117,5 @@ internal class OneOfLegacyGenerationTests { }""" ) ) - } } } \ No newline at end of file diff --git a/src/test/kotlin/TestServer.kt b/src/test/kotlin/TestServer.kt index 6c1377ca7..5aff02116 100644 --- a/src/test/kotlin/TestServer.kt +++ b/src/test/kotlin/TestServer.kt @@ -34,7 +34,6 @@ import com.papsign.ktor.openapigen.route.apiRouting import com.papsign.ktor.openapigen.route.info import com.papsign.ktor.openapigen.route.path.normal.get import com.papsign.ktor.openapigen.route.path.normal.post -import com.papsign.ktor.openapigen.route.path.normal.route import com.papsign.ktor.openapigen.route.response.respond import com.papsign.ktor.openapigen.route.responseAnnotationStatus import com.papsign.ktor.openapigen.route.route @@ -46,8 +45,6 @@ import com.papsign.ktor.openapigen.schema.namer.SchemaNamer import io.ktor.http.HttpStatusCode import io.ktor.serialization.jackson.jackson import io.ktor.server.application.Application -import io.ktor.server.application.application -import io.ktor.server.application.call import io.ktor.server.application.install import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty @@ -58,6 +55,7 @@ import io.ktor.server.request.host import io.ktor.server.request.port import io.ktor.server.response.respond import io.ktor.server.response.respondRedirect +import io.ktor.server.routing.application import io.ktor.server.routing.get import io.ktor.server.routing.routing import java.time.Instant @@ -345,7 +343,7 @@ object TestServer { ).contains(call.request.port()) ) "" else ":${call.request.port()}" ) - this.application.openAPIGen.api.servers.add(0, host) + application.openAPIGen.api.servers.add(0, host) call.respond(application.openAPIGen.api.serialize()) application.openAPIGen.api.servers.remove(host) } diff --git a/src/test/kotlin/TestServerWithJwtAuth.kt b/src/test/kotlin/TestServerWithJwtAuth.kt index c936a3230..8a3b69416 100644 --- a/src/test/kotlin/TestServerWithJwtAuth.kt +++ b/src/test/kotlin/TestServerWithJwtAuth.kt @@ -30,9 +30,6 @@ import com.papsign.ktor.openapigen.schema.namer.DefaultSchemaNamer import com.papsign.ktor.openapigen.schema.namer.SchemaNamer import io.ktor.serialization.jackson.jackson import io.ktor.server.application.Application -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.application -import io.ktor.server.application.call import io.ktor.server.application.install import io.ktor.server.auth.Authentication import io.ktor.server.auth.AuthenticationConfig @@ -48,9 +45,10 @@ import io.ktor.server.request.host import io.ktor.server.request.port import io.ktor.server.response.respond import io.ktor.server.response.respondRedirect +import io.ktor.server.routing.RoutingContext +import io.ktor.server.routing.application import io.ktor.server.routing.get import io.ktor.server.routing.routing -import io.ktor.util.pipeline.PipelineContext import java.net.URL import java.util.concurrent.TimeUnit import kotlin.reflect.KType @@ -189,8 +187,8 @@ object TestServerWithJwtAuth { ) ) - override suspend fun getAuth(pipeline: PipelineContext): UserPrincipal { - return pipeline.context.authentication.principal() ?: throw RuntimeException("No JWTPrincipal") + override suspend fun getAuth(pipeline: RoutingContext): UserPrincipal { + return pipeline.call.authentication.principal() ?: throw RuntimeException("No JWTPrincipal") } override fun apply(route: NormalOpenAPIRoute): OpenAPIAuthenticatedRoute { diff --git a/src/test/kotlin/com/papsign/ktor/openapigen/EnumNonStrictTestServer.kt b/src/test/kotlin/com/papsign/ktor/openapigen/EnumNonStrictTestServer.kt index a9261ce44..24ae9ad1f 100644 --- a/src/test/kotlin/com/papsign/ktor/openapigen/EnumNonStrictTestServer.kt +++ b/src/test/kotlin/com/papsign/ktor/openapigen/EnumNonStrictTestServer.kt @@ -7,13 +7,17 @@ import com.papsign.ktor.openapigen.exceptions.OpenAPIRequiredFieldException import com.papsign.ktor.openapigen.route.apiRouting import com.papsign.ktor.openapigen.route.path.normal.get import com.papsign.ktor.openapigen.route.response.respond -import io.ktor.http.* +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application import io.ktor.server.application.install import io.ktor.server.plugins.statuspages.StatusPages import io.ktor.server.response.respond -import io.ktor.server.testing.* -import kotlin.test.* +import io.ktor.server.testing.testApplication +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue enum class NonStrictTestEnum { VALID, @@ -67,98 +71,116 @@ class NonStrictEnumTestServer { } @Test - fun `nullable enum could be omitted and it will be null`() { - withTestApplication({ nullableEnum() }) { - handleRequest(HttpMethod.Get, "/").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("null", response.content) - } + fun `nullable enum could be omitted and it will be null`() = testApplication { + application { + nullableEnum() } + + val response = client.get("/") + + assertEquals(HttpStatusCode.OK, response.status) + assertEquals("null", response.bodyAsText()) } @Test - fun `nullable enum should be parsed correctly`() { - withTestApplication({ nullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=VALID").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("VALID", response.content) - } - handleRequest(HttpMethod.Get, "/?type=ALSO_VALID").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("ALSO_VALID", response.content) - } + fun `nullable enum should be parsed correctly`() = testApplication { + application { + nullableEnum() + } + + client.get("/?type=VALID").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("VALID", bodyAsText()) + } + client.get("/?type=ALSO_VALID").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("ALSO_VALID", bodyAsText()) } } @Test - fun `nullable enum parsing should be case-sensitive and should return 200 with null result`() { - withTestApplication({ nullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=valid").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("null", response.content) - } - handleRequest(HttpMethod.Get, "/?type=also_valid").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("null", response.content) - } + fun `nullable enum parsing should be case-sensitive and should return 200 with null result`() = testApplication { + application { + nullableEnum() + } + + client.get("/?type=valid").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("null", bodyAsText()) + } + client.get("/?type=also_valid").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("null", bodyAsText()) } } @Test - fun `nullable enum parsing should return 200 with null result on parse values outside of enum`() { - withTestApplication({ nullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=what").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("null", response.content) - } + fun `nullable enum parsing should return 200 with null result on parse values outside of enum`() = testApplication { + application { + nullableEnum() + } + + client.get("/?type=what").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("null", bodyAsText()) } } @Test - fun `non-nullable enum cannot be omitted`() { - withTestApplication({ nonNullableEnum() }) { - handleRequest(HttpMethod.Get, "/").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals("The field type is required", response.content) - } + fun `non-nullable enum cannot be omitted`() = testApplication { + application { + nonNullableEnum() + } + + client.get("/").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("The field type is required", bodyAsText()) } } @Test - fun `non-nullable enum should be parsed correctly`() { - withTestApplication({ nonNullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=VALID").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("VALID", response.content) - } - handleRequest(HttpMethod.Get, "/?type=ALSO_VALID").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("ALSO_VALID", response.content) - } + fun `non-nullable enum should be parsed correctly`() = testApplication { + application { + nonNullableEnum() + } + + client.get("/?type=VALID").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("VALID", bodyAsText()) + } + + client.get("/?type=ALSO_VALID").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("ALSO_VALID", bodyAsText()) } } @Test - fun `non-nullable enum parsing should be case-sensitive and should throw on passing wrong case`() { - withTestApplication({ nonNullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=valid").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals("The field type is required", response.content) - } - handleRequest(HttpMethod.Get, "/?type=also_valid").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals("The field type is required", response.content) - } + fun `non-nullable enum parsing should be case-sensitive and should throw on passing wrong case`() = testApplication { + application { + nonNullableEnum() + } + + client.get("/?type=valid").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("The field type is required", bodyAsText()) + } + + client.get("/?type=also_valid").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("The field type is required", bodyAsText()) } } @Test - fun `non-nullable enum parsing should not parse values outside of enum`() { - withTestApplication({ nonNullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=what").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals("The field type is required", response.content) - } + fun `non-nullable enum parsing should not parse values outside of enum`() = testApplication { + application { + nonNullableEnum() + } + + client.get("/?type=what").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("The field type is required", bodyAsText()) } } } diff --git a/src/test/kotlin/com/papsign/ktor/openapigen/EnumStrictTestServer.kt b/src/test/kotlin/com/papsign/ktor/openapigen/EnumStrictTestServer.kt index 39fcb3664..6e0f159df 100644 --- a/src/test/kotlin/com/papsign/ktor/openapigen/EnumStrictTestServer.kt +++ b/src/test/kotlin/com/papsign/ktor/openapigen/EnumStrictTestServer.kt @@ -8,13 +8,17 @@ import com.papsign.ktor.openapigen.exceptions.OpenAPIRequiredFieldException import com.papsign.ktor.openapigen.route.apiRouting import com.papsign.ktor.openapigen.route.path.normal.get import com.papsign.ktor.openapigen.route.response.respond -import io.ktor.http.* +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application import io.ktor.server.application.install import io.ktor.server.plugins.statuspages.StatusPages import io.ktor.server.response.respond -import io.ktor.server.testing.* -import kotlin.test.* +import io.ktor.server.testing.testApplication +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue @StrictEnumParsing enum class StrictTestEnum { @@ -69,116 +73,115 @@ class EnumStrictTestServer { } @Test - fun `nullable enum could be omitted and it will be null`() { - withTestApplication({ nullableEnum() }) { - handleRequest(HttpMethod.Get, "/").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("null", response.content) - } + fun `nullable enum could be omitted and it will be null`() = testApplication { + application { + nullableEnum() + } + + client.get("/").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("null", bodyAsText()) } } @Test - fun `nullable enum should be parsed correctly`() { - withTestApplication({ nullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=VALID").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("VALID", response.content) - } - handleRequest(HttpMethod.Get, "/?type=ALSO_VALID").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("ALSO_VALID", response.content) - } + fun `nullable enum should be parsed correctly`() = testApplication { + application { + nullableEnum() + } + + client.get("/?type=VALID").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("VALID", bodyAsText()) + } + client.get("/?type=ALSO_VALID").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("ALSO_VALID", bodyAsText()) } } @Test - fun `nullable enum parsing should be case-sensitive and should throw on passing wrong case`() { - withTestApplication({ nullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=valid").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals( - "Invalid value [valid] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", - response.content - ) - } - handleRequest(HttpMethod.Get, "/?type=also_valid").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals( - "Invalid value [also_valid] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", - response.content - ) - } + fun `nullable enum parsing should be case-sensitive and should throw on passing wrong case`() = testApplication { + application { + nullableEnum() + } + + client.get("/?type=valid").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("Invalid value [valid] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", bodyAsText()) + } + client.get("/?type=also_valid").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("Invalid value [also_valid] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", bodyAsText()) } } @Test - fun `nullable enum parsing should not parse values outside of enum`() { - withTestApplication({ nullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=what").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals( - "Invalid value [what] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", - response.content - ) - } + fun `nullable enum parsing should not parse values outside of enum`() = testApplication { + application { + nullableEnum() + } + + client.get("/?type=what").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("Invalid value [what] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", bodyAsText()) } } @Test - fun `non-nullable enum cannot be omitted`() { - withTestApplication({ nonNullableEnum() }) { - handleRequest(HttpMethod.Get, "/").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals("The field type is required", response.content) - } + fun `non-nullable enum cannot be omitted`() = testApplication { + application { + nonNullableEnum() + } + + client.get("/").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("The field type is required", bodyAsText()) } } @Test - fun `non-nullable enum should be parsed correctly`() { - withTestApplication({ nonNullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=VALID").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("VALID", response.content) - } - handleRequest(HttpMethod.Get, "/?type=ALSO_VALID").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertEquals("ALSO_VALID", response.content) - } + fun `non-nullable enum should be parsed correctly`() = testApplication { + application { + nonNullableEnum() + } + + client.get("/?type=VALID").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("VALID", bodyAsText()) + } + + client.get("/?type=ALSO_VALID").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("ALSO_VALID", bodyAsText()) } } @Test - fun `non-nullable enum parsing should be case-sensitive and should throw on passing wrong case`() { - withTestApplication({ nonNullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=valid").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals( - "Invalid value [valid] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", - response.content - ) - } - handleRequest(HttpMethod.Get, "/?type=also_valid").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals( - "Invalid value [also_valid] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", - response.content - ) - } + fun `non-nullable enum parsing should be case-sensitive and should throw on passing wrong case`() = testApplication { + application { + nonNullableEnum() + } + + client.get("/?type=valid").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("Invalid value [valid] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", bodyAsText()) + } + client.get("/?type=also_valid").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("Invalid value [also_valid] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", bodyAsText()) } } @Test - fun `non-nullable enum parsing should not parse values outside of enum`() { - withTestApplication({ nonNullableEnum() }) { - handleRequest(HttpMethod.Get, "/?type=what").apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) - assertEquals( - "Invalid value [what] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", - response.content - ) - } + fun `non-nullable enum parsing should not parse values outside of enum`() = testApplication { + application { + nonNullableEnum() + } + + client.get("/?type=what").apply { + assertEquals(HttpStatusCode.BadRequest, status) + assertEquals("Invalid value [what] for enum parameter of type StrictTestEnum. Expected: [VALID,ALSO_VALID]", bodyAsText()) } } } diff --git a/src/test/kotlin/com/papsign/ktor/openapigen/FormDocumentationGenerationTest.kt b/src/test/kotlin/com/papsign/ktor/openapigen/FormDocumentationGenerationTest.kt index 318e8a620..1bb9f2789 100644 --- a/src/test/kotlin/com/papsign/ktor/openapigen/FormDocumentationGenerationTest.kt +++ b/src/test/kotlin/com/papsign/ktor/openapigen/FormDocumentationGenerationTest.kt @@ -1,5 +1,6 @@ package com.papsign.ktor.openapigen +import TestServer import TestServer.setupBaseTestServer import com.papsign.ktor.openapigen.content.type.multipart.FormDataRequest import com.papsign.ktor.openapigen.content.type.multipart.FormDataRequestType @@ -8,32 +9,34 @@ import com.papsign.ktor.openapigen.route.apiRouting import com.papsign.ktor.openapigen.route.path.normal.post import com.papsign.ktor.openapigen.route.response.respond import com.papsign.ktor.openapigen.route.route -import io.ktor.http.* -import io.ktor.server.application.log -import io.ktor.server.testing.* -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import io.ktor.server.testing.testApplication import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue internal class FormDocumentationGenerationTest { @Test - fun formDataTestRequest() = withTestApplication({ - setupBaseTestServer() - apiRouting { - route("form-data"){ - post{ _, _ -> - respond(TestServer.StringResponse("result")) + fun formDataTestRequest() = testApplication { + application { + setupBaseTestServer() + apiRouting { + route("form-data") { + post { _, _ -> + respond(TestServer.StringResponse("result")) + } } } } - }) { - with(handleRequest(HttpMethod.Get, "//openapi.json")) { - this@withTestApplication.application.log.debug(response.content) - assertEquals(HttpStatusCode.OK, response.status()) - assertTrue( - response.content!!.contains( - """ "paths" : { + + val response = client.get("/openapi.json") + assertEquals(HttpStatusCode.OK, response.status) + assertTrue( + response.bodyAsText().contains( + """ "paths" : { "/form-data" : { "post" : { "requestBody" : { @@ -45,29 +48,29 @@ internal class FormDocumentationGenerationTest { } } },""" - ) ) - - } + ) } @Test - fun multipartFormDataTestRequest() = withTestApplication({ - setupBaseTestServer() - apiRouting { - route("multipart-data"){ - post{ _, _ -> - respond(TestServer.StringResponse("result")) + fun multipartFormDataTestRequest() = testApplication { + application { + setupBaseTestServer() + apiRouting { + route("multipart-data") { + post { _, _ -> + respond(TestServer.StringResponse("result")) + } } } } - }) { - with(handleRequest(HttpMethod.Get, "//openapi.json")) { - this@withTestApplication.application.log.debug(response.content) - assertEquals(HttpStatusCode.OK, response.status()) - assertTrue( - response.content!!.contains( - """ "paths" : { + + val response = client.get("/openapi.json") + + assertEquals(HttpStatusCode.OK, response.status) + assertTrue( + response.bodyAsText().contains( + """ "paths" : { "/multipart-data" : { "post" : { "requestBody" : { @@ -79,28 +82,28 @@ internal class FormDocumentationGenerationTest { } } },""" - ) ) - - } + ) } @Test - fun defaultFormDataTestRequest() = withTestApplication({ - setupBaseTestServer() - apiRouting { - route("default-form-data"){ - post{ _, _ -> - respond(TestServer.StringResponse("result")) + fun defaultFormDataTestRequest() = testApplication { + application { + setupBaseTestServer() + apiRouting { + route("default-form-data") { + post { _, _ -> + respond(TestServer.StringResponse("result")) + } } } } - }) { - with(handleRequest(HttpMethod.Get, "//openapi.json")) { - this@withTestApplication.application.log.debug(response.content) - assertEquals(HttpStatusCode.OK, response.status()) + + val response = client.get("/openapi.json") + + assertEquals(HttpStatusCode.OK, response.status) assertTrue( - response.content!!.contains( + response.bodyAsText().contains( """ "paths" : { "/default-form-data" : { "post" : { @@ -116,7 +119,6 @@ internal class FormDocumentationGenerationTest { ) ) - } } } diff --git a/src/test/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryContentTypeParserTest.kt b/src/test/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryContentTypeParserTest.kt index cc5d7eee0..c4a001c00 100644 --- a/src/test/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryContentTypeParserTest.kt +++ b/src/test/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryContentTypeParserTest.kt @@ -5,15 +5,20 @@ import com.papsign.ktor.openapigen.route.path.normal.post import com.papsign.ktor.openapigen.route.response.respond import com.papsign.ktor.openapigen.route.route import installOpenAPI -import io.ktor.http.* -import io.ktor.server.testing.contentType -import io.ktor.server.testing.handleRequest -import io.ktor.server.testing.setBody -import io.ktor.server.testing.withTestApplication -import org.junit.Assert.* +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.readRawBytes +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.server.testing.testApplication +import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Test import java.io.InputStream import kotlin.random.Random +import kotlin.test.assertEquals const val contentType = "image/png" @@ -28,63 +33,65 @@ class BinaryContentTypeParserTest { fun testBinaryParsing() { val route = "/test" val bytes = Random.nextBytes(20) - withTestApplication({ - installOpenAPI() - apiRouting { - //(this.ktorRoute as Routing).trace { println(it.buildText()) } - route(route) { - post { _, body -> - val actual = body.stream.readBytes() - assertArrayEquals(bytes, actual) - respond(Stream(actual.inputStream())) + testApplication { + application { + installOpenAPI() + apiRouting { + //(this.ktorRoute as Routing).trace { println(it.buildText()) } + route(route) { + post { _, body -> + val actual = body.stream.readBytes() + assertArrayEquals(bytes, actual) + respond(Stream(actual.inputStream())) + } } } } - }) { + println("Test: Normal") - handleRequest(HttpMethod.Post, route) { - addHeader(HttpHeaders.ContentType, contentType) - addHeader(HttpHeaders.Accept, contentType) + client.post(route) { + header(HttpHeaders.ContentType, contentType) + header(HttpHeaders.Accept, contentType) setBody(bytes) - }.apply { + }.let { response -> assertEquals(ContentType.parse(contentType), response.contentType()) - assertArrayEquals(bytes, response.byteContent) + assertArrayEquals(bytes, response.readRawBytes()) } println("Test: Missing Accept") - handleRequest(HttpMethod.Post, route) { - addHeader(HttpHeaders.ContentType, contentType) + client.post(route) { + header(HttpHeaders.ContentType, contentType) setBody(bytes) - }.apply { + }.let { response -> assertEquals(ContentType.parse(contentType), response.contentType()) - assertArrayEquals(bytes, response.byteContent) + assertArrayEquals(bytes, response.readRawBytes()) } println("Test: Missing Content-Type") - handleRequest(HttpMethod.Post, route) { - addHeader(HttpHeaders.Accept, contentType) + client.post(route) { + header(HttpHeaders.Accept, contentType) setBody(bytes) - }.apply { - assertEquals(HttpStatusCode.UnsupportedMediaType, response.status()) + }.let { response -> + assertEquals(HttpStatusCode.UnsupportedMediaType, response.status) } println("Test: Bad Accept") - handleRequest(HttpMethod.Post, route) { - addHeader(HttpHeaders.ContentType, contentType) - addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString()) + client.post(route) { + header(HttpHeaders.ContentType, contentType) + header(HttpHeaders.Accept, ContentType.Application.Json.toString()) setBody(bytes) - }.apply { - assertEquals(HttpStatusCode.BadRequest, response.status()) + }.let { response -> + assertEquals(HttpStatusCode.BadRequest, response.status) } println("Test: Bad Content-Type") - handleRequest(HttpMethod.Post, route) { - addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString()) - addHeader(HttpHeaders.Accept, contentType) + client.post(route) { + header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + header(HttpHeaders.Accept, contentType) setBody(bytes) - }.apply { - assertEquals(HttpStatusCode.UnsupportedMediaType, response.status()) + }.let { response -> + assertEquals(HttpStatusCode.UnsupportedMediaType, response.status) } } } diff --git a/src/test/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProviderTest.kt b/src/test/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProviderTest.kt index 2d2a9e12c..aa774380d 100644 --- a/src/test/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProviderTest.kt +++ b/src/test/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProviderTest.kt @@ -7,16 +7,31 @@ import com.papsign.ktor.openapigen.route.response.respond import com.papsign.ktor.openapigen.route.route import installJackson import installOpenAPI -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.server.testing.* -import org.junit.Assert.assertEquals +import io.ktor.client.request.forms.MultiPartFormDataContent +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentDisposition +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.content.PartData +import io.ktor.http.headersOf +import io.ktor.server.testing.testApplication import org.junit.jupiter.api.Test -import java.time.* +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime +import java.time.ZoneOffset +import java.time.ZonedDateTime import java.util.* import kotlin.reflect.KProperty1 import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.memberProperties +import kotlin.test.assertEquals class MultipartFormDataContentProviderTest { @@ -147,31 +162,39 @@ class MultipartFormDataContentProviderTest { ) ) ) - withTestApplication({ - installOpenAPI() - installJackson() - apiRouting { - requests.forEach { (t, u) -> - route(t) { - post { _, body -> - assertEquals(u.first, body) - respond(true) + testApplication { + application { + installOpenAPI() + installJackson() + apiRouting { + requests.forEach { (t, u) -> + route(t) { + post { _, body -> + assertEquals(u.first, body) + respond(true) + } } } } } - }) { requests.forEach { (t, u) -> println("Test: $t") - handleRequest(HttpMethod.Post, t) { + + client.post(t) { val boundary = "***bbb***" - addHeader( + header( HttpHeaders.ContentType, ContentType.MultiPart.FormData.withParameter("boundary", boundary).toString() ) - setBody(boundary, u.second) - }.apply { - assertEquals(true, response.content?.toBoolean()) + + setBody( + MultiPartFormDataContent( + boundary = boundary, + parts = u.second, + ) + ) + }.let { response -> + assertEquals(true, response.bodyAsText().toBoolean()) } println("Test: $t success") } diff --git a/src/test/kotlin/com/papsign/ktor/openapigen/routing/GenericRoutesTest.kt b/src/test/kotlin/com/papsign/ktor/openapigen/routing/GenericRoutesTest.kt index 2e6e7da65..0b020c9cf 100644 --- a/src/test/kotlin/com/papsign/ktor/openapigen/routing/GenericRoutesTest.kt +++ b/src/test/kotlin/com/papsign/ktor/openapigen/routing/GenericRoutesTest.kt @@ -7,16 +7,31 @@ import com.papsign.ktor.openapigen.model.security.SecuritySchemeModel import com.papsign.ktor.openapigen.model.security.SecuritySchemeType import com.papsign.ktor.openapigen.modules.providers.AuthProvider import com.papsign.ktor.openapigen.route.apiRouting -import com.papsign.ktor.openapigen.route.path.auth.* -import com.papsign.ktor.openapigen.route.path.normal.* +import com.papsign.ktor.openapigen.route.path.auth.OpenAPIAuthenticatedRoute +import com.papsign.ktor.openapigen.route.path.auth.delete +import com.papsign.ktor.openapigen.route.path.auth.get +import com.papsign.ktor.openapigen.route.path.auth.patch +import com.papsign.ktor.openapigen.route.path.auth.post +import com.papsign.ktor.openapigen.route.path.normal.NormalOpenAPIRoute +import com.papsign.ktor.openapigen.route.path.normal.delete +import com.papsign.ktor.openapigen.route.path.normal.get +import com.papsign.ktor.openapigen.route.path.normal.patch +import com.papsign.ktor.openapigen.route.path.normal.post import com.papsign.ktor.openapigen.route.response.respond import com.papsign.ktor.openapigen.route.route import com.papsign.ktor.openapigen.route.throws import installJackson import installOpenAPI -import io.ktor.http.* -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.request +import io.ktor.client.request.setBody +import io.ktor.client.statement.HttpResponse +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType import io.ktor.server.application.install import io.ktor.server.auth.Authentication import io.ktor.server.auth.UserIdPrincipal @@ -24,8 +39,8 @@ import io.ktor.server.auth.authenticate import io.ktor.server.auth.authentication import io.ktor.server.auth.basic import io.ktor.server.response.respond -import io.ktor.server.testing.* -import io.ktor.util.pipeline.* +import io.ktor.server.routing.RoutingContext +import io.ktor.server.testing.testApplication import org.junit.jupiter.api.Test import java.util.* import kotlin.test.assertEquals @@ -34,8 +49,8 @@ import kotlin.test.assertTrue class GenericRoutesTest { @Test - fun genericRoutesTest() { - withTestApplication({ + fun genericRoutesTest() = testApplication { + application { installOpenAPI() installJackson() @@ -52,7 +67,6 @@ class GenericRoutesTest { } } - val service = ObjectService apiRouting { route("objects") { @@ -64,61 +78,66 @@ class GenericRoutesTest { } } } - }) { - handleRequest(HttpMethod.Get,"/objects").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertTrue { response.contentType().match("application/json") } - } - handleRequest(HttpMethod.Get,"/objects/1").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertTrue { response.contentType().match("application/json") } - } - handleRequest(HttpMethod.Post,"/objects") { - addHeader(HttpHeaders.ContentType, "application/json") - addHeader(HttpHeaders.Accept, "application/json") - setBody(""" { "name": "test" } """) - }.apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertTrue { response.contentType().match("application/json") } - } + } - fun handleRequestWithBasic(method: HttpMethod, uri: String, setup: TestApplicationRequest.() -> Unit = {}): TestApplicationCall { - return handleRequest(method, uri) { - val up = Base64.getEncoder().encodeToString("jetbrains:foobar".toByteArray()) - addHeader(HttpHeaders.Authorization, "Basic $up") - setup() - } - } - handleRequest(HttpMethod.Get,"/private/objects").apply { - assertEquals(HttpStatusCode.Unauthorized, response.status()) - } + client.get("/objects").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + assertTrue { response.contentType()!!.match("application/json") } + } + client.get("/objects/1").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + assertTrue { response.contentType()!!.match("application/json") } + } + client.get("/objects") { + header(HttpHeaders.ContentType, "application/json") + header(HttpHeaders.Accept, "application/json") + setBody(""" { "name": "test" } """) + }.let { response -> + assertEquals(HttpStatusCode.OK, response.status) + assertTrue { response.contentType()!!.match("application/json") } + } - handleRequestWithBasic(HttpMethod.Get,"/private/objects") { - }.apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertTrue { response.contentType().match("application/json") } - } - handleRequestWithBasic(HttpMethod.Get,"/private/objects/1").apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertTrue { response.contentType().match("application/json") } - } - handleRequestWithBasic(HttpMethod.Post,"/private/objects") { - addHeader(HttpHeaders.ContentType, "application/json") - addHeader(HttpHeaders.Accept, "application/json") - setBody(""" { "name": "test" } """) - }.apply { - assertEquals(HttpStatusCode.OK, response.status()) - assertTrue { response.contentType().match("application/json") } + suspend fun handleRequestWithBasic( + method: HttpMethod, + uri: String, + setup: HttpRequestBuilder.() -> Unit = {} + ): HttpResponse { + return client.request(uri) { + this.method = method + val up = Base64.getEncoder().encodeToString("jetbrains:foobar".toByteArray()) + header(HttpHeaders.Authorization, "Basic $up") + setup() } + } + + client.get("/private/objects").let { response -> + assertEquals(HttpStatusCode.Unauthorized, response.status) + } + handleRequestWithBasic(HttpMethod.Get, "/private/objects").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + assertTrue { response.contentType()!!.match("application/json") } + } + handleRequestWithBasic(HttpMethod.Get, "/private/objects/1").let { response -> + assertEquals(HttpStatusCode.OK, response.status) + assertTrue { response.contentType()!!.match("application/json") } + } + handleRequestWithBasic(HttpMethod.Post, "/private/objects") { + header(HttpHeaders.ContentType, "application/json") + header(HttpHeaders.Accept, "application/json") + setBody(""" { "name": "test" } """) + }.let { response -> + assertEquals(HttpStatusCode.OK, response.status) + assertTrue { response.contentType()!!.match("application/json") } } + } data class ObjectNewDto(override val name: String, override val parentId: Long?) : TreeNodeNew - data class ObjectDto(override val name: String, override val parentId: Long?, override val id: Long): TreeNodeBase + data class ObjectDto(override val name: String, override val parentId: Long?, override val id: Long) : TreeNodeBase object ObjectService : TreeNodeService { override fun listNodes(parentId: Long?): List { @@ -229,7 +248,11 @@ class GenericRoutesTest { enum class Scopes : Described data class ResponseError(val code: Int, val description: String, val message: String? = null) { - constructor(statusCode: HttpStatusCode, message: String? = null) : this(statusCode.value, statusCode.description, message) + constructor(statusCode: HttpStatusCode, message: String? = null) : this( + statusCode.value, + statusCode.description, + message + ) } object BasicAuthProvider : AuthProvider { @@ -249,8 +272,8 @@ class GenericRoutesTest { ) // gets auth information at runtime - override suspend fun getAuth(pipeline: PipelineContext): UserIdPrincipal { - return pipeline.context.authentication.principal() + override suspend fun getAuth(pipeline: RoutingContext): UserIdPrincipal { + return pipeline.call.authentication.principal() ?: throw UnauthorizedException("Unable to verify given credentials, or credentials are missing.") } @@ -259,7 +282,12 @@ class GenericRoutesTest { return OpenAPIAuthenticatedRoute(route.ktorRoute.authenticate { }, route.provider.child(), this) .throws( status = HttpStatusCode.Unauthorized.description("Your identity could not be verified."), - gen = { e: UnauthorizedException -> return@throws ResponseError(HttpStatusCode.Unauthorized, e.message) } + gen = { e: UnauthorizedException -> + return@throws ResponseError( + HttpStatusCode.Unauthorized, + e.message + ) + } ) .throws( status = HttpStatusCode.Forbidden.description("Your access rights are insufficient."), diff --git a/src/test/kotlin/com/papsign/ktor/openapigen/routing/GenericsTest.kt b/src/test/kotlin/com/papsign/ktor/openapigen/routing/GenericsTest.kt index cea6d79cc..31a1473d4 100644 --- a/src/test/kotlin/com/papsign/ktor/openapigen/routing/GenericsTest.kt +++ b/src/test/kotlin/com/papsign/ktor/openapigen/routing/GenericsTest.kt @@ -1,5 +1,8 @@ package com.papsign.ktor.openapigen.routing +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue import com.papsign.ktor.openapigen.annotations.parameters.HeaderParam import com.papsign.ktor.openapigen.route.apiRouting import com.papsign.ktor.openapigen.route.path.normal.post @@ -12,14 +15,11 @@ import io.ktor.client.request.post import io.ktor.client.request.setBody import io.ktor.client.statement.bodyAsText import io.ktor.http.HttpHeaders -import io.ktor.http.HttpMethod +import io.ktor.http.contentType import io.ktor.server.routing.Routing -import io.ktor.server.testing.contentType -import io.ktor.server.testing.handleRequest -import io.ktor.server.testing.setBody import io.ktor.server.testing.testApplication -import io.ktor.server.testing.withTestApplication import org.junit.jupiter.api.Test +import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -30,28 +30,29 @@ class GenericsTest { @Test fun testTypedMap() { val route = "/test" - withTestApplication({ - installOpenAPI() - installJackson() - apiRouting { - (this.ktorRoute as Routing).trace { println(it.buildText()) } - route(route) { - post, Map> { params, body -> - respond(mutableListOf(params.toString(), body.toString())) + testApplication { + application { + installOpenAPI() + installJackson() + apiRouting { + (this.ktorRoute as Routing).trace { println(it.buildText()) } + route(route) { + post, Map> { params, body -> + respond(mutableListOf(params.toString(), body.toString())) + } } } } - }) { - handleRequest(HttpMethod.Post, route) { - addHeader(HttpHeaders.ContentType, "application/json") - addHeader(HttpHeaders.Accept, "application/json") - addHeader("Test-Header", "1,2,3") + client.post(route) { + header(HttpHeaders.ContentType, "application/json") + header(HttpHeaders.Accept, "application/json") + header("Test-Header", "1,2,3") setBody("{\"xyz\":456}") - }.apply { - assertTrue { response.contentType().match("application/json") } + }.let { response -> + assertTrue { response.contentType()!!.match("application/json") } assertEquals( "[\"TestHeaderParams(Test-Header=[1, 2, 3])\",\"{xyz=456}\"]", - response.content + response.bodyAsText() ) } } @@ -60,28 +61,30 @@ class GenericsTest { @Test fun testTypedList() { val route = "/test" - withTestApplication({ - installOpenAPI() - installJackson() - apiRouting { - (this.ktorRoute as Routing).trace { println(it.buildText()) } - route(route) { - post, List> { params, body -> - respond(mutableListOf(params.toString(), body.toString())) + testApplication { + application { + installOpenAPI() + installJackson() + apiRouting { + (this.ktorRoute as Routing).trace { println(it.buildText()) } + route(route) { + post, List> { params, body -> + respond(mutableListOf(params.toString(), body.toString())) + } } } } - }) { - handleRequest(HttpMethod.Post, route) { - addHeader(HttpHeaders.ContentType, "application/json") - addHeader(HttpHeaders.Accept, "application/json") - addHeader("Test-Header", "1,2,3") + + client.post(route) { + header(HttpHeaders.ContentType, "application/json") + header(HttpHeaders.Accept, "application/json") + header("Test-Header", "1,2,3") setBody("[\"a\",\"b\",\"c\"]") - }.apply { - assertTrue { response.contentType().match("application/json") } + }.let { response -> + assertTrue { response.contentType()!!.match("application/json") } assertEquals( "[\"TestHeaderParams(Test-Header=[1, 2, 3])\",\"[a, b, c]\"]", - response.content + response.bodyAsText() ) } } @@ -97,7 +100,7 @@ class GenericsTest { apiRouting { (this.ktorRoute as Routing).trace { println(it.buildText()) } route(route) { - post { params, body -> + post { _, body -> respond(body) } } @@ -117,5 +120,14 @@ class GenericsTest { } } - class StringList(private val list: List): List by list + @Test + fun testTypedListSubclassSerialization() { + val jackson = jacksonObjectMapper() + assertEquals("""["a","b","c"]""", jackson.writeValueAsString(StringList(listOf("a", "b", "c")))) + assertContentEquals(StringList(listOf("a", "b", "c")), jackson.readValue("""["a","b","c"]""")) + } + + class StringList + @JsonCreator + constructor(private val list: List) : List by list } diff --git a/src/test/kotlin/com/papsign/ktor/openapigen/routing/RoutingTest.kt b/src/test/kotlin/com/papsign/ktor/openapigen/routing/RoutingTest.kt index 61619cfcf..10dbc2ede 100644 --- a/src/test/kotlin/com/papsign/ktor/openapigen/routing/RoutingTest.kt +++ b/src/test/kotlin/com/papsign/ktor/openapigen/routing/RoutingTest.kt @@ -8,15 +8,17 @@ import com.papsign.ktor.openapigen.route.response.respond import com.papsign.ktor.openapigen.route.route import installJackson import installOpenAPI +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText import io.ktor.http.HttpHeaders -import io.ktor.http.HttpMethod +import io.ktor.http.contentType import io.ktor.server.routing.Routing -import io.ktor.server.testing.contentType -import io.ktor.server.testing.handleRequest -import io.ktor.server.testing.setBody -import io.ktor.server.testing.withTestApplication -import org.junit.Assert.assertEquals +import io.ktor.server.testing.testApplication import org.junit.jupiter.api.Test +import kotlin.test.assertEquals import kotlin.test.assertTrue class RoutingTest { @@ -29,28 +31,30 @@ class RoutingTest { @Test fun testPostWithHeaderAndBodyParams() { val route = "/test" - withTestApplication({ - installOpenAPI() - installJackson() - apiRouting { - (this.ktorRoute as Routing).trace { println(it.buildText()) } - route(route) { - post { params, body -> - respond(TestResponse("$params -> $body")) + testApplication { + + application { + installOpenAPI() + installJackson() + apiRouting { + (this.ktorRoute as Routing).trace { println(it.buildText()) } + route(route) { + post { params, body -> + respond(TestResponse("$params -> $body")) + } } } } - }) { - handleRequest(HttpMethod.Post, route) { - addHeader(HttpHeaders.ContentType, "application/json") - addHeader(HttpHeaders.Accept, "application/json") - addHeader("test-header", "123") + client.post(route) { + header(HttpHeaders.ContentType, "application/json") + header(HttpHeaders.Accept, "application/json") + header("test-header", "123") setBody("{\"xyz\":456}") - }.apply { - assertTrue { response.contentType().match("application/json") } + }.let { response -> + assertTrue { response.contentType()!!.match("application/json") } assertEquals( "{\"msg\":\"${TestHeaderParams(123)} -> ${TestBodyParams(456)}\"}", - response.content + response.bodyAsText(), ) } } @@ -59,26 +63,27 @@ class RoutingTest { @Test fun testGetWithHeaderParams() { val route = "/test" - withTestApplication({ - installOpenAPI() - installJackson() - apiRouting { - (this.ktorRoute as Routing).trace { println(it.buildText()) } - route(route) { - get { params -> - respond(TestResponse("$params")) + testApplication { + application { + installOpenAPI() + installJackson() + apiRouting { + (this.ktorRoute as Routing).trace { println(it.buildText()) } + route(route) { + get { params -> + respond(TestResponse("$params")) + } } } } - }) { - handleRequest(HttpMethod.Get, route) { - addHeader(HttpHeaders.Accept, "application/json") - addHeader("Test-Header", "123") - }.apply { - assertTrue { response.contentType().match("application/json") } + client.get(route) { + header(HttpHeaders.Accept, "application/json") + header("Test-Header", "123") + }.let { response -> + assertTrue { response.contentType()!!.match("application/json") } assertEquals( "{\"msg\":\"${TestHeaderParams2(123)}\"}", - response.content + response.bodyAsText() ) } } @@ -87,25 +92,26 @@ class RoutingTest { @Test fun testPostWithUnitTypes() { val route = "/test" - withTestApplication({ - installOpenAPI() - installJackson() - apiRouting { - (this.ktorRoute as Routing).trace { println(it.buildText()) } - route(route) { - post { params, body -> - respond(TestResponse("Test Response")) + testApplication { + application { + installOpenAPI() + installJackson() + apiRouting { + (this.ktorRoute as Routing).trace { println(it.buildText()) } + route(route) { + post { params, body -> + respond(TestResponse("Test Response")) + } } } } - }) { - handleRequest(HttpMethod.Post, route) { - addHeader(HttpHeaders.Accept, "application/json") - }.apply { - assertTrue { response.contentType().match("application/json") } + client.post(route) { + header(HttpHeaders.Accept, "application/json") + }.let { response -> + assertTrue { response.contentType()!!.match("application/json") } assertEquals( "{\"msg\":\"Test Response\"}", - response.content + response.bodyAsText(), ) } } @@ -114,25 +120,26 @@ class RoutingTest { @Test fun testGetWithUnitTypes() { val route = "/test" - withTestApplication({ - installOpenAPI() - installJackson() - apiRouting { - (this.ktorRoute as Routing).trace { println(it.buildText()) } - route(route) { - get { params -> - respond(TestResponse("Test Response")) + testApplication { + application { + installOpenAPI() + installJackson() + apiRouting { + (this.ktorRoute as Routing).trace { println(it.buildText()) } + route(route) { + get { params -> + respond(TestResponse("Test Response")) + } } } } - }) { - handleRequest(HttpMethod.Get, route) { - addHeader(HttpHeaders.Accept, "application/json") - }.apply { - assertTrue { response.contentType().match("application/json") } + client.get(route) { + header(HttpHeaders.Accept, "application/json") + }.let { response -> + assertTrue { response.contentType()!!.match("application/json") } assertEquals( "{\"msg\":\"Test Response\"}", - response.content + response.bodyAsText() ) } }