From 26c481e1a4e1922d3f51a8f0e16a76a93e386bfb Mon Sep 17 00:00:00 2001 From: Jevgeni Goloborodko Date: Fri, 1 Apr 2022 16:21:50 +0300 Subject: [PATCH] fix: swagger ui regression from dependency bump --- CHANGELOG.md | 2 + .../kotlin/io/bkbn/kompendium/core/Kontent.kt | 2 - .../playground/SwaggerPlayground.kt | 14 ++-- kompendium-swagger-ui/build.gradle.kts | 5 ++ .../io/bkbn/kompendium/swagger/JsConfig.kt | 2 +- .../io/bkbn/kompendium/swagger/SwaggerUI.kt | 4 +- .../kompendium/swagger/SwaggerWebJarUtils.kt | 6 +- .../bkbn/kompendium/swagger/SwaggerUiTest.kt | 54 +++++++++++++ .../io/bkbn/kompendium/swagger/TestHelpers.kt | 79 +++++++++++++++++++ 9 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/SwaggerUiTest.kt create mode 100644 kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/TestHelpers.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 858b1b3aa..55526d4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,10 @@ ## Unreleased ### Added +- Added tests for Swagger UI module that verify that plugin generates correct responses for Swagger UI WEB resources (tests should detect future incompatible changes in new versions of `org.webjars.swagger-ui`) ### Changed +- Fixed broken Swagger UI plugin (`org.webjars.swagger-ui` WEB resources structure changed in version 4.9.X). Issue: https://github.com/bkbnio/kompendium/issues/236 ### Remove diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kontent.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kontent.kt index 5c28af427..685aed752 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kontent.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kontent.kt @@ -1,6 +1,5 @@ package io.bkbn.kompendium.core -import io.bkbn.kompendium.annotations.constraint.Format import io.bkbn.kompendium.core.handler.CollectionHandler import io.bkbn.kompendium.core.handler.EnumHandler import io.bkbn.kompendium.core.handler.MapHandler @@ -12,7 +11,6 @@ import io.bkbn.kompendium.oas.schema.SimpleSchema import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.full.createType -import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.isSubclassOf import kotlin.reflect.typeOf import org.slf4j.LoggerFactory diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SwaggerPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SwaggerPlayground.kt index 12a936fb1..72415e06c 100644 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SwaggerPlayground.kt +++ b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SwaggerPlayground.kt @@ -72,13 +72,13 @@ private fun Application.mainModule() { // Example is prepared according to this documentation: https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/ jsInit = { """ - ui.initOAuth({ - clientId: 'CLIENT_ID', - clientSecret: 'CLIENT_SECRET', - realm: 'MY REALM', - appName: 'TEST APP', - useBasicAuthenticationWithAccessCodeGrant: true - }); + window.ui.initOAuth({ + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + realm: 'MY REALM', + appName: 'TEST APP', + useBasicAuthenticationWithAccessCodeGrant: true + }); """ } ) diff --git a/kompendium-swagger-ui/build.gradle.kts b/kompendium-swagger-ui/build.gradle.kts index d9abe0413..8e02fe375 100644 --- a/kompendium-swagger-ui/build.gradle.kts +++ b/kompendium-swagger-ui/build.gradle.kts @@ -7,6 +7,7 @@ plugins { id("maven-publish") id("java-library") id("signing") + id("java-test-fixtures") } sourdough { @@ -16,9 +17,13 @@ sourdough { dependencies { val ktorVersion: String by project + + implementation(projects.kompendiumCore) implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion) implementation(group = "org.webjars", name = "webjars-locator-core", version = "0.50") implementation(group = "org.webjars", name = "swagger-ui", version = "4.9.1") + + testImplementation(testFixtures(projects.kompendiumCore)) } testing { diff --git a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsConfig.kt b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsConfig.kt index f43ad914b..d4c90b246 100644 --- a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsConfig.kt +++ b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsConfig.kt @@ -23,7 +23,7 @@ data class JsConfig( internal fun JsConfig.toJsProps(): String = asMap() .filterKeys { !setOf("specs", "jsInit").contains(it) } .map{ "${it.key}: ${it.value.toJs()}" } - .joinToString(separator = ",\n\t\t") + .joinToString(separator = ",\n ") internal fun JsConfig.getSpecUrlsProps(): String = if (specs.isEmpty()) "[]" else specs.map { "{url: ${it.value.toJs()}, name: ${it.key.toJs()}}" }.toString() diff --git a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerUI.kt b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerUI.kt index 16600c1e4..2bb90273d 100644 --- a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerUI.kt +++ b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerUI.kt @@ -56,8 +56,8 @@ class SwaggerUI(val config: Configuration) { get("${config.swaggerBaseUrl}/{filename}") { call.parameters["filename"]!!.let { filename -> when(filename) { - "index.html" -> - locator.getSwaggerIndexContent(jsConfig = config.jsConfig) + "swagger-initializer.js" -> + locator.getSwaggerInitializerContent(jsConfig = config.jsConfig) else -> locator.getSwaggerResourceContent(path = filename) } diff --git a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerWebJarUtils.kt b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerWebJarUtils.kt index 1b3632042..1397e1493 100644 --- a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerWebJarUtils.kt +++ b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerWebJarUtils.kt @@ -12,13 +12,13 @@ internal fun WebJarAssetLocator.getSwaggerResource(path: String): URL = internal fun WebJarAssetLocator.getSwaggerResourceContent(path: String): ByteArrayContent = ByteArrayContent(getSwaggerResource(path).readBytes()) -internal fun WebJarAssetLocator.getSwaggerIndexContent(jsConfig: JsConfig): ByteArrayContent = ByteArrayContent( - getSwaggerResource(path = "index.html").readText() +internal fun WebJarAssetLocator.getSwaggerInitializerContent(jsConfig: JsConfig): ByteArrayContent = ByteArrayContent( + getSwaggerResource(path = "swagger-initializer.js").readText() .replaceFirst("url: \"https://petstore.swagger.io/v2/swagger.json\",", "urls: ${jsConfig.getSpecUrlsProps()},") .replaceFirst("deepLinking: true", jsConfig.toJsProps()) .let { content -> jsConfig.jsInit()?.let { - content.replaceFirst("window.ui = ui", "$it\n\twindow.ui = ui") + content.replaceFirst("});", "});\n$it") } ?: content }.toByteArray() ) diff --git a/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/SwaggerUiTest.kt b/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/SwaggerUiTest.kt new file mode 100644 index 000000000..00113b2f1 --- /dev/null +++ b/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/SwaggerUiTest.kt @@ -0,0 +1,54 @@ +package io.bkbn.kompendium.swagger + +import io.bkbn.kompendium.swagger.TestHelpers.TEST_SWAGGER_UI_INDEX +import io.bkbn.kompendium.swagger.TestHelpers.TEST_SWAGGER_UI_INIT_JS +import io.bkbn.kompendium.swagger.TestHelpers.TEST_SWAGGER_UI_ROOT +import io.bkbn.kompendium.swagger.TestHelpers.compareRedirect +import io.bkbn.kompendium.swagger.TestHelpers.compareResource +import io.bkbn.kompendium.swagger.TestHelpers.withSwaggerApplication +import io.kotest.core.spec.style.DescribeSpec + +class SwaggerUiTest: DescribeSpec ({ + + describe("Swagger UI resources") { + + it ("Redirects /swagger-ui -> index.html") { + withSwaggerApplication { + compareRedirect(TEST_SWAGGER_UI_ROOT, TEST_SWAGGER_UI_INDEX) + } + } + + it ("Can return original: index.html") { + withSwaggerApplication { + compareResource(TEST_SWAGGER_UI_INDEX, listOf( + "Swagger UI", + "
", + "src=\"./swagger-initializer.js\"" + )) + } + } + + it("Can return generated: swagger-initializer.js") { + withSwaggerApplication { + compareResource(TEST_SWAGGER_UI_INIT_JS, listOf( + "url: '/openapi.json', name: 'My API v1'", + "url: '/openapi.json', name: 'My API v2'", + "defaultModelExpandDepth: 4", + "defaultModelsExpandDepth: 4", + "displayOperationId: true", + "displayRequestDuration: true", + "operationsSorter: 'alpha'", + "persistAuthorization: true", + "tagsSorter: 'alpha'", + "window.ui.initOAuth", + "clientId: 'CLIENT_ID'", + "clientSecret: 'CLIENT_SECRET'", + "realm: 'MY REALM'", + "appName: 'TEST APP'", + "useBasicAuthenticationWithAccessCodeGrant: true" + )) + } + } + } + +}) diff --git a/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/TestHelpers.kt b/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/TestHelpers.kt new file mode 100644 index 000000000..dd63347c2 --- /dev/null +++ b/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/TestHelpers.kt @@ -0,0 +1,79 @@ +package io.bkbn.kompendium.swagger + +import io.bkbn.kompendium.core.Kompendium +import io.bkbn.kompendium.core.fixtures.kompendium +import io.kotest.assertions.ktor.shouldHaveStatus +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain +import io.ktor.application.Application +import io.ktor.application.install +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.server.testing.TestApplicationEngine +import io.ktor.server.testing.createTestEnvironment +import io.ktor.server.testing.handleRequest +import io.ktor.server.testing.withApplication +import java.net.URI + +object TestHelpers { + + const val TEST_SWAGGER_UI_ROOT = "/swagger-ui" + const val TEST_SWAGGER_UI_INDEX = "/webjars/swagger-ui/index.html" + const val TEST_SWAGGER_UI_INIT_JS = "/webjars/swagger-ui/swagger-initializer.js" + + // The same config the same as in Playground + private val basicSwaggerUiConfig: SwaggerUI.Configuration.() -> Unit = { + swaggerUrl = "/swagger-ui" + jsConfig = JsConfig( + specs = mapOf( + "My API v1" to URI("/openapi.json"), + "My API v2" to URI("/openapi.json") + ), + jsInit = { + """ + window.ui.initOAuth({ + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + realm: 'MY REALM', + appName: 'TEST APP', + useBasicAuthenticationWithAccessCodeGrant: true + }); + """ + } + ) + } + + fun TestApplicationEngine.compareRedirect(resourceName: String, targetResource: String) { + handleRequest(HttpMethod.Get, resourceName).apply { + response shouldHaveStatus HttpStatusCode.Found + response.headers[HttpHeaders.Location] shouldBe targetResource + } + } + + fun TestApplicationEngine.compareResource(resourceName: String, mustContain: List) { + handleRequest(HttpMethod.Get, resourceName).apply { + response shouldHaveStatus HttpStatusCode.OK + response.content shouldNotBe null + mustContain.forEach { + response.content!! shouldContain it + } + } + } + + fun withSwaggerApplication( + moduleFunction: Application.() -> Unit = {}, + kompendiumConfigurer: Kompendium.Configuration.() -> Unit = {}, + swaggerUIConfigurer: SwaggerUI.Configuration.() -> Unit = basicSwaggerUiConfig, + test: TestApplicationEngine.() -> R + ) { + withApplication(createTestEnvironment()) { + moduleFunction(application.apply { + kompendium(kompendiumConfigurer) + install(SwaggerUI, swaggerUIConfigurer) + }) + test() + } + } +}