fix: swagger ui regression from dependency bump
This commit is contained in:

committed by
GitHub

parent
7a1e57f50c
commit
26c481e1a4
@ -3,8 +3,10 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Added
|
### 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
|
### 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
|
### Remove
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.bkbn.kompendium.core
|
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.CollectionHandler
|
||||||
import io.bkbn.kompendium.core.handler.EnumHandler
|
import io.bkbn.kompendium.core.handler.EnumHandler
|
||||||
import io.bkbn.kompendium.core.handler.MapHandler
|
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.KClass
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.full.createType
|
import kotlin.reflect.full.createType
|
||||||
import kotlin.reflect.full.findAnnotation
|
|
||||||
import kotlin.reflect.full.isSubclassOf
|
import kotlin.reflect.full.isSubclassOf
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
@ -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/
|
// Example is prepared according to this documentation: https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/
|
||||||
jsInit = {
|
jsInit = {
|
||||||
"""
|
"""
|
||||||
ui.initOAuth({
|
window.ui.initOAuth({
|
||||||
clientId: 'CLIENT_ID',
|
clientId: 'CLIENT_ID',
|
||||||
clientSecret: 'CLIENT_SECRET',
|
clientSecret: 'CLIENT_SECRET',
|
||||||
realm: 'MY REALM',
|
realm: 'MY REALM',
|
||||||
appName: 'TEST APP',
|
appName: 'TEST APP',
|
||||||
useBasicAuthenticationWithAccessCodeGrant: true
|
useBasicAuthenticationWithAccessCodeGrant: true
|
||||||
});
|
});
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -7,6 +7,7 @@ plugins {
|
|||||||
id("maven-publish")
|
id("maven-publish")
|
||||||
id("java-library")
|
id("java-library")
|
||||||
id("signing")
|
id("signing")
|
||||||
|
id("java-test-fixtures")
|
||||||
}
|
}
|
||||||
|
|
||||||
sourdough {
|
sourdough {
|
||||||
@ -16,9 +17,13 @@ sourdough {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val ktorVersion: String by project
|
val ktorVersion: String by project
|
||||||
|
|
||||||
|
implementation(projects.kompendiumCore)
|
||||||
implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion)
|
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 = "webjars-locator-core", version = "0.50")
|
||||||
implementation(group = "org.webjars", name = "swagger-ui", version = "4.9.1")
|
implementation(group = "org.webjars", name = "swagger-ui", version = "4.9.1")
|
||||||
|
|
||||||
|
testImplementation(testFixtures(projects.kompendiumCore))
|
||||||
}
|
}
|
||||||
|
|
||||||
testing {
|
testing {
|
||||||
|
@ -23,7 +23,7 @@ data class JsConfig(
|
|||||||
internal fun JsConfig.toJsProps(): String = asMap()
|
internal fun JsConfig.toJsProps(): String = asMap()
|
||||||
.filterKeys { !setOf("specs", "jsInit").contains(it) }
|
.filterKeys { !setOf("specs", "jsInit").contains(it) }
|
||||||
.map{ "${it.key}: ${it.value.toJs()}" }
|
.map{ "${it.key}: ${it.value.toJs()}" }
|
||||||
.joinToString(separator = ",\n\t\t")
|
.joinToString(separator = ",\n ")
|
||||||
|
|
||||||
internal fun JsConfig.getSpecUrlsProps(): String =
|
internal fun JsConfig.getSpecUrlsProps(): String =
|
||||||
if (specs.isEmpty()) "[]" else specs.map { "{url: ${it.value.toJs()}, name: ${it.key.toJs()}}" }.toString()
|
if (specs.isEmpty()) "[]" else specs.map { "{url: ${it.value.toJs()}, name: ${it.key.toJs()}}" }.toString()
|
||||||
|
@ -56,8 +56,8 @@ class SwaggerUI(val config: Configuration) {
|
|||||||
get("${config.swaggerBaseUrl}/{filename}") {
|
get("${config.swaggerBaseUrl}/{filename}") {
|
||||||
call.parameters["filename"]!!.let { filename ->
|
call.parameters["filename"]!!.let { filename ->
|
||||||
when(filename) {
|
when(filename) {
|
||||||
"index.html" ->
|
"swagger-initializer.js" ->
|
||||||
locator.getSwaggerIndexContent(jsConfig = config.jsConfig)
|
locator.getSwaggerInitializerContent(jsConfig = config.jsConfig)
|
||||||
else ->
|
else ->
|
||||||
locator.getSwaggerResourceContent(path = filename)
|
locator.getSwaggerResourceContent(path = filename)
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,13 @@ internal fun WebJarAssetLocator.getSwaggerResource(path: String): URL =
|
|||||||
internal fun WebJarAssetLocator.getSwaggerResourceContent(path: String): ByteArrayContent =
|
internal fun WebJarAssetLocator.getSwaggerResourceContent(path: String): ByteArrayContent =
|
||||||
ByteArrayContent(getSwaggerResource(path).readBytes())
|
ByteArrayContent(getSwaggerResource(path).readBytes())
|
||||||
|
|
||||||
internal fun WebJarAssetLocator.getSwaggerIndexContent(jsConfig: JsConfig): ByteArrayContent = ByteArrayContent(
|
internal fun WebJarAssetLocator.getSwaggerInitializerContent(jsConfig: JsConfig): ByteArrayContent = ByteArrayContent(
|
||||||
getSwaggerResource(path = "index.html").readText()
|
getSwaggerResource(path = "swagger-initializer.js").readText()
|
||||||
.replaceFirst("url: \"https://petstore.swagger.io/v2/swagger.json\",", "urls: ${jsConfig.getSpecUrlsProps()},")
|
.replaceFirst("url: \"https://petstore.swagger.io/v2/swagger.json\",", "urls: ${jsConfig.getSpecUrlsProps()},")
|
||||||
.replaceFirst("deepLinking: true", jsConfig.toJsProps())
|
.replaceFirst("deepLinking: true", jsConfig.toJsProps())
|
||||||
.let { content ->
|
.let { content ->
|
||||||
jsConfig.jsInit()?.let {
|
jsConfig.jsInit()?.let {
|
||||||
content.replaceFirst("window.ui = ui", "$it\n\twindow.ui = ui")
|
content.replaceFirst("});", "});\n$it")
|
||||||
} ?: content
|
} ?: content
|
||||||
}.toByteArray()
|
}.toByteArray()
|
||||||
)
|
)
|
||||||
|
@ -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(
|
||||||
|
"<title>Swagger UI</title>",
|
||||||
|
"<div id=\"swagger-ui\"></div>",
|
||||||
|
"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"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
@ -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<String>) {
|
||||||
|
handleRequest(HttpMethod.Get, resourceName).apply {
|
||||||
|
response shouldHaveStatus HttpStatusCode.OK
|
||||||
|
response.content shouldNotBe null
|
||||||
|
mustContain.forEach {
|
||||||
|
response.content!! shouldContain it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <R> 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user