diff --git a/components/proxy/src/test/kotlin/com/hotels/styx/Support.kt b/components/proxy/src/test/kotlin/com/hotels/styx/Support.kt index b12b8b304..08e83700a 100644 --- a/components/proxy/src/test/kotlin/com/hotels/styx/Support.kt +++ b/components/proxy/src/test/kotlin/com/hotels/styx/Support.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -46,11 +46,14 @@ import io.mockk.CapturingSlot import io.mockk.every import io.mockk.mockk import org.slf4j.LoggerFactory +import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.toMono import java.nio.charset.StandardCharsets.UTF_8 import java.util.concurrent.CompletableFuture import java.util.concurrent.Executor +fun Mono.blockRequired(): T = block() ?: error("Expected a value but Mono was empty") + fun routingObjectDef(text: String) = YamlConfig(text).`as`(StyxObjectDefinition::class.java) fun configBlock(text: String) = YamlConfig(text).`as`(JsonNode::class.java) @@ -177,29 +180,29 @@ fun mockObjectFactory(objects: List) = mockk.wait(debug: Boolean = false) = this.toMono() +fun CompletableFuture.wait(debug: Boolean = false): HttpResponse = this.toMono() .doOnNext { if (debug) { LOGGER.debug("${it.status()} - ${it.headers()} - ${it.bodyAs(UTF_8)}") } } - .block() + .blockRequired() -fun CompletableFuture.wait(debug: Boolean = false) = this.toMono() +fun CompletableFuture.wait(debug: Boolean = false): LiveHttpResponse = this.toMono() .doOnNext { if (debug) { LOGGER.debug("${it.status()} - ${it.headers()}") } } - .block() + .blockRequired() -fun Eventual.wait(maxBytes: Int = 100 * 1024, debug: Boolean = false) = this.toMono() +fun Eventual.wait(maxBytes: Int = 100 * 1024, debug: Boolean = false): HttpResponse = this.toMono() .flatMap { it.aggregate(maxBytes).toMono() } .doOnNext { if (debug) { LOGGER.info("${it.status()} - ${it.headers()} - ${it.bodyAs(UTF_8)}") } } - .block() + .blockRequired() fun requestContext(secure: Boolean = false, executor: Executor = Executor { it.run() }) = HttpInterceptorContext(secure, null, executor) diff --git a/components/proxy/src/test/kotlin/com/hotels/styx/admin/handlers/RoutingObjectHandlerTest.kt b/components/proxy/src/test/kotlin/com/hotels/styx/admin/handlers/RoutingObjectHandlerTest.kt index d655349c0..15ca4407e 100644 --- a/components/proxy/src/test/kotlin/com/hotels/styx/admin/handlers/RoutingObjectHandlerTest.kt +++ b/components/proxy/src/test/kotlin/com/hotels/styx/admin/handlers/RoutingObjectHandlerTest.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import com.hotels.styx.api.HttpRequest.put import com.hotels.styx.api.HttpResponseStatus.CREATED import com.hotels.styx.api.HttpResponseStatus.NOT_FOUND import com.hotels.styx.api.HttpResponseStatus.OK +import com.hotels.styx.blockRequired import com.hotels.styx.handle import com.hotels.styx.mockObject import com.hotels.styx.routing.RoutingMetadataDecorator @@ -59,7 +60,7 @@ class RoutingObjectHandlerTest : FeatureSpec({ .body(staticResponseObject, UTF_8) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe CREATED routeDatabase.get("staticResponse").isPresent shouldBe true @@ -76,14 +77,14 @@ class RoutingObjectHandlerTest : FeatureSpec({ .body(staticResponseObject, UTF_8) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe CREATED handler.handle(get("/admin/routing/objects/staticResponse").build()) .toMono() - .block() + .blockRequired() .let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8).trim() shouldBe """ --- type: "StaticResponseHandler" @@ -106,7 +107,7 @@ class RoutingObjectHandlerTest : FeatureSpec({ .body(staticResponseObject, UTF_8) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe CREATED handler.handle( @@ -121,7 +122,7 @@ class RoutingObjectHandlerTest : FeatureSpec({ """.trimIndent(), UTF_8) .build()) .toMono() - .block() + .blockRequired() .let { it.status() shouldBe CREATED } @@ -130,9 +131,9 @@ class RoutingObjectHandlerTest : FeatureSpec({ handler.handle(get("/admin/routing/objects").build()) .toMono() - .block() + .blockRequired() .let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldContain """ conditionRouter: type: "ConditionRouter" @@ -175,7 +176,7 @@ class RoutingObjectHandlerTest : FeatureSpec({ """.trimIndent(), UTF_8) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe CREATED db.get("staticResponse").isPresent shouldBe true @@ -195,7 +196,7 @@ class RoutingObjectHandlerTest : FeatureSpec({ handler.handle( delete("/admin/routing/objects/staticResponse").build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe OK db.get("staticResponse").isPresent shouldBe false @@ -211,7 +212,7 @@ class RoutingObjectHandlerTest : FeatureSpec({ handler.handle( delete("/admin/routing/objects/staticResponse").build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe NOT_FOUND } diff --git a/components/proxy/src/test/kotlin/com/hotels/styx/routing/db/StyxObjectStoreTest.kt b/components/proxy/src/test/kotlin/com/hotels/styx/routing/db/StyxObjectStoreTest.kt index 2ee10847a..a9eccf8aa 100644 --- a/components/proxy/src/test/kotlin/com/hotels/styx/routing/db/StyxObjectStoreTest.kt +++ b/components/proxy/src/test/kotlin/com/hotels/styx/routing/db/StyxObjectStoreTest.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2025 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/components/proxy/src/test/kotlin/com/hotels/styx/routing/handlers/LoadBalancingGroupTest.kt b/components/proxy/src/test/kotlin/com/hotels/styx/routing/handlers/LoadBalancingGroupTest.kt index 07fd1b150..ee2526b53 100644 --- a/components/proxy/src/test/kotlin/com/hotels/styx/routing/handlers/LoadBalancingGroupTest.kt +++ b/components/proxy/src/test/kotlin/com/hotels/styx/routing/handlers/LoadBalancingGroupTest.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,8 +20,10 @@ import com.hotels.styx.api.HttpHandler import com.hotels.styx.api.HttpHeaders import com.hotels.styx.api.HttpRequest import com.hotels.styx.api.HttpRequest.get +import com.hotels.styx.api.HttpResponse import com.hotels.styx.api.configuration.ObjectStore import com.hotels.styx.api.exceptions.NoAvailableHostsException +import com.hotels.styx.blockRequired import com.hotels.styx.handle import com.hotels.styx.lbGroupTag import com.hotels.styx.requestContext @@ -179,7 +181,7 @@ class LoadBalancingGroupTest : FeatureSpec() { routeDb.get("appx-B").get().routingObject.metric().ongoingActivities() shouldBe 20 invocations.forEach { - val response = it.block() + val response = it.blockRequired() LOGGER.debug("response: ${response.bodyAs(UTF_8)}") } } @@ -215,7 +217,7 @@ internal fun Publisher>.waitUntil(duration: Dur .blockFirst(duration) -internal fun HttpHandler.call(request: HttpRequest, maxContentBytes: Int = 100000) = this.handle(request.stream(), requestContext()) +internal fun HttpHandler.call(request: HttpRequest, maxContentBytes: Int = 100000): HttpResponse = this.handle(request.stream(), requestContext()) .flatMap { it.aggregate(maxContentBytes) } .toMono() - .block() + .blockRequired() diff --git a/components/proxy/src/test/kotlin/com/hotels/styx/servers/StyxHttpServerTest.kt b/components/proxy/src/test/kotlin/com/hotels/styx/servers/StyxHttpServerTest.kt index 9f6b955e0..635549155 100644 --- a/components/proxy/src/test/kotlin/com/hotels/styx/servers/StyxHttpServerTest.kt +++ b/components/proxy/src/test/kotlin/com/hotels/styx/servers/StyxHttpServerTest.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package com.hotels.styx.servers +import com.hotels.styx.blockRequired import com.hotels.styx.InetServer import com.hotels.styx.RoutingObjectFactoryContext import com.hotels.styx.StyxObjectRecord @@ -73,7 +74,7 @@ class StyxHttpServerTest : FeatureSpec({ .header(HOST, "localhost:${server.inetAddress()!!.port}") .build()) .wait() - ?.let { + .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "Hello, test!" } @@ -104,7 +105,7 @@ class StyxHttpServerTest : FeatureSpec({ .header(HOST, "localhost:${server.inetAddress()!!.port}") .build()) .wait() - ?.let { + .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "Hello, test!" } @@ -130,7 +131,7 @@ class StyxHttpServerTest : FeatureSpec({ .header(HOST, "localhost:${server.inetAddress()!!.port}") .header("accept-encoding", "7z, gzip") .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.header("content-encoding").get() shouldBe "gzip" @@ -142,7 +143,7 @@ class StyxHttpServerTest : FeatureSpec({ StyxHttpClient.Builder().build().send(get("/blah") .header(HOST, "localhost:${server.inetAddress()!!.port}") .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.header("content-encoding").isPresent shouldBe false @@ -168,7 +169,7 @@ class StyxHttpServerTest : FeatureSpec({ StyxHttpClient.Builder().build().send(get("/a/" + "b".repeat(80)) .header(HOST, "localhost:${server.inetAddress()!!.port}") .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "Hello, test!" @@ -178,8 +179,8 @@ class StyxHttpServerTest : FeatureSpec({ scenario("Rejects requests exceeding the initial line length") { StyxHttpClient.Builder().build().send(get("/a/" + "b".repeat(95)) .header(HOST, "localhost:${server.inetAddress()!!.port}") - .build())!! - .wait()!! + .build()) + .wait() .let { it.status() shouldBe REQUEST_ENTITY_TOO_LARGE it.bodyAs(UTF_8) shouldBe "Request Entity Too Large" @@ -205,7 +206,7 @@ class StyxHttpServerTest : FeatureSpec({ StyxHttpClient.Builder().build().send(get("/a/" + "b".repeat(80)) .header(HOST, "localhost:${server.inetAddress()!!.port}") .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "Hello, test!" @@ -217,8 +218,8 @@ class StyxHttpServerTest : FeatureSpec({ .header(HOST, "localhost:${server.inetAddress()!!.port}") .header("test-1", "x".repeat(1024)) .header("test-2", "x".repeat(1024)) - .build())!! - .wait()!! + .build()) + .wait() .let { it.status() shouldBe REQUEST_ENTITY_TOO_LARGE it.bodyAs(UTF_8) shouldBe "Request Entity Too Large" @@ -252,8 +253,8 @@ class StyxHttpServerTest : FeatureSpec({ .build()) .wait() .aggregate(1024) - .toFlux() - .blockFirst()!! + .toMono() + .blockRequired() .let { it.status() shouldBe REQUEST_TIMEOUT it.header(CONNECTION).get() shouldBe "close" @@ -281,7 +282,7 @@ class StyxHttpServerTest : FeatureSpec({ .createConnection( newOriginBuilder("localhost", server.inetAddress()!!.port).build(), ConnectionSettings(250)) - .block()!! + .blockRequired() scenario("Should keep HTTP1/1 client connection open after serving the response.") { connection.write( @@ -290,10 +291,10 @@ class StyxHttpServerTest : FeatureSpec({ .build() .stream(), DummyContext) .toMono() - .block()!! + .blockRequired() .aggregate(1024) .toMono() - .block()!! + .blockRequired() Thread.sleep(100) @@ -348,8 +349,8 @@ class StyxHttpServerTest : FeatureSpec({ StyxHttpClient.Builder().build().send(get("/a/" + "b".repeat(95)) .header(HOST, "localhost:${server.inetAddress()!!.port}") - .build())!! - .wait()!! + .build()) + .wait() .let { it.status() shouldBe OK } @@ -374,7 +375,7 @@ fun threadNames() = Thread.getAllStackTraces().keys private fun createConnection(port: Int) = NettyConnectionFactory.Builder() .build() .createConnection(newOriginBuilder("localhost", port).build(), ConnectionSettings(250)) - .block()!! + .blockRequired() private val response = response(OK) .header("source", "secure") diff --git a/pom.xml b/pom.xml index e92a6da16..05832828d 100644 --- a/pom.xml +++ b/pom.xml @@ -114,7 +114,7 @@ 4.12.0 5.0.0 1.0.4 - 2024.0.17 + 2025.0.5 2.3.21 diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/OriginsFileCompatibilitySpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/OriginsFileCompatibilitySpec.kt index 7dd9fcbe7..a8f60094a 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/OriginsFileCompatibilitySpec.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/OriginsFileCompatibilitySpec.kt @@ -113,7 +113,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK } } } @@ -122,7 +122,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { client.send(get("/admin/providers/originsFileLoader/configuration") .header(HOST, styxServer().adminHostHeader()) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe originsFile @@ -168,7 +168,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appA-01" } } @@ -178,7 +178,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appA-02" } } @@ -199,7 +199,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appA-01" } } @@ -222,7 +222,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appB-01" } } @@ -231,7 +231,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appA-01" } } @@ -253,7 +253,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appB-01" } } @@ -262,7 +262,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appA-01" } } @@ -280,7 +280,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appA-01" } } @@ -297,7 +297,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appA-02" } } @@ -328,7 +328,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appB-01" } } @@ -337,14 +337,14 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appC-01" } client.send(get("/c/def/hello2") .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appC-01" } @@ -396,7 +396,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK } } @@ -405,7 +405,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .cookies(requestCookie("ABC", "appA.appA-02")) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appA-02" @@ -427,7 +427,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { client.send(get("/") .header(HOST, styxServer().adminHostHeader()) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldNotContain ("/admin/dashboard/") @@ -436,7 +436,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { client.send(get("/admin/dashboard/index.html") .header(HOST, styxServer().adminHostHeader()) .build()) - .wait()!! + .wait() .let { // Admin index page (with links) it.bodyAs(UTF_8) shouldContain """
  • Configuration
  • """ @@ -445,7 +445,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { client.send(get("/admin/dashboard/data.json") .header(HOST, styxServer().adminHostHeader()) .build()) - .wait()!! + .wait() .let { // Admin index page (with links) it.bodyAs(UTF_8) shouldContain """
  • Configuration
  • """ @@ -460,7 +460,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { client.send(get("/admin/providers/originsFileLoader/configuration") .header(HOST, styxServer().adminHostHeader()) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe validOriginsFile @@ -480,7 +480,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { client.send(get("/admin/providers/originsFileLoader/configuration") .header(HOST, styxServer().adminHostHeader()) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe validOriginsFile @@ -533,7 +533,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appB-01" Thread.sleep(200) } @@ -557,7 +557,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { client.send(get("/16") .header(HOST, styxServer().proxyHttpHostHeader()) .build()) - .wait()!! + .wait() .let { it.status() shouldBe BAD_GATEWAY it.header("X-Styx-Origin-Id").get() shouldBe "appB" @@ -570,7 +570,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().adminHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK val monitors = extractHealthCheckMonitors(it.bodyAs(UTF_8)) monitors shouldHaveSize 1 validateHealthCheckMonitor(monitors[0]) @@ -582,7 +582,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().adminHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK val monitor = deserialiseHealthCheckMonitor(it.bodyAs(UTF_8)) validateHealthCheckMonitor(monitor) } @@ -593,7 +593,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().adminHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.header(CONTENT_TYPE).get().lowercase() shouldBe APPLICATION_JSON.toString().lowercase() // TODO: This name should probably change. it.bodyAs(UTF_8) shouldBe "{ name: \"HealthCheckMonitoringService-appB\" status: \"RUNNING\" }" @@ -617,7 +617,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appB-01" Thread.sleep(200) } @@ -629,7 +629,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().adminHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK val monitors = extractHealthCheckMonitors(it.bodyAs(UTF_8)) monitors shouldHaveSize 0 } @@ -640,7 +640,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().adminHostHeader()) .build()) .wait().let { - it!!.status() shouldBe NOT_FOUND + it.status() shouldBe NOT_FOUND } } } @@ -692,7 +692,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { client.send(get("/foo") .header(HOST, styxServer().proxyHttpHostHeader()) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK } @@ -732,7 +732,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { client.send(get("/foo/2") .header(HOST, styxServer().proxyHttpHostHeader()) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK } @@ -761,7 +761,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK } } @@ -778,7 +778,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK } } @@ -796,7 +796,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .wait().let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "appA-02" } } @@ -815,7 +815,7 @@ class OriginsFileCompatibilitySpec : FunSpec() { .header(HOST, styxServer().adminHostHeader()) .build()) .wait() - .bodyAs(UTF_8)!! + .bodyAs(UTF_8) val mockServerA01 = MockOriginServer.create("appA", "appA-01", 0, HttpConnectorConfig(0)) .start() diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/ConditionRoutingSpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/ConditionRoutingSpec.kt index 649c509f4..be3411024 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/ConditionRoutingSpec.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/ConditionRoutingSpec.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import com.hotels.styx.api.HttpRequest.get import com.hotels.styx.api.HttpResponseStatus.OK import com.hotels.styx.client.StyxHttpClient import com.hotels.styx.support.StyxServerProvider +import com.hotels.styx.support.blockRequired import com.hotels.styx.support.proxyHttpHostHeader import com.hotels.styx.support.proxyHttpsHostHeader import io.kotest.core.spec.Spec @@ -38,9 +39,9 @@ class ConditionRoutingSpec : StringSpec() { client.send(request) .toMono() - .block() + .blockRequired() .let { - it!!.status() shouldBe (OK) + it.status() shouldBe (OK) it.bodyAs(UTF_8) shouldBe ("Hello, from http server!") } @@ -55,9 +56,9 @@ class ConditionRoutingSpec : StringSpec() { client.secure() .send(request) .toMono() - .block() + .blockRequired() .let { - it!!.status() shouldBe (OK) + it.status() shouldBe (OK) it.bodyAs(UTF_8) shouldBe ("Hello, from secure server!") } } diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/LoadBalancingGroupSpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/LoadBalancingGroupSpec.kt index bc0efa5dd..8b08a0643 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/LoadBalancingGroupSpec.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/LoadBalancingGroupSpec.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ import io.kotest.assertions.withClue import io.kotest.core.spec.Spec import io.kotest.core.spec.style.FeatureSpec import io.kotest.matchers.booleans.shouldBeTrue +import io.kotest.matchers.collections.shouldBeIn import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldMatch @@ -189,7 +190,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { for (i in 1..50) { withClue("Response should come from active instances only") { client.send(get("/").header(HOST, styxServer().proxyHttpHostHeader()).build()) - .wait(debug = false)!!.bodyAs(UTF_8) shouldBe "mock-server-03" + .wait(debug = false).bodyAs(UTF_8) shouldBe "mock-server-03" } } } @@ -220,7 +221,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { withClue("Load balancing group shouldn't have been configured yet") { client.send(get("/").header(HOST, styxServer().proxyHttpHostHeader()).build()) - .wait()!! + .wait() .let { it.status() shouldBe (BAD_GATEWAY) } @@ -239,7 +240,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { withClue("Couldn't get a response from mock-server-01") { eventually(1.seconds) { client.send(get("/").header(HOST, styxServer().proxyHttpHostHeader()).build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "mock-server-01" @@ -251,7 +252,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { scenario("... and detects removed origins") { withClue("Origin is not reachable") { client.send(get("/").header(HOST, styxServer().proxyHttpHostHeader()).build()) - .wait()!! + .wait() .let { it.status() shouldBe (OK) } @@ -261,9 +262,9 @@ class LoadBalancingGroupSpec : FeatureSpec() { withClue("Origin is still reachable after removal") { client.send(get("/").header(HOST, styxServer().proxyHttpHostHeader()).build()) - .wait()!! + .wait() .let { - it.status() shouldBe (BAD_GATEWAY) + it.status().code() shouldBeIn listOf(500, 502) } } @@ -325,7 +326,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { scenario("Responds with sticky session cookie when STICKY_SESSION_ENABLED=true") { client.send(get("/sticky/").header(HOST, styxServer().proxyHttpHostHeader()).build()) - .wait()!! + .wait() .cookie("styx_origin_stickyApp") .get() .let { @@ -339,7 +340,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { scenario("Responds without sticky session cookie when sticky session is not enabled") { client.send(get("/normal/").header(HOST, styxServer().proxyHttpHostHeader()).build()) - .wait()!! + .wait() .cookie("styx_origin_app") shouldBe Optional.empty() } @@ -349,7 +350,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .cookies(requestCookie("styx_origin_stickyApp", "app-A-02")) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe ("mock-server-02") @@ -366,7 +367,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { requestCookie("other_cookie2", "bar"), requestCookie("styx_origin_stickyApp", "app-A-02")) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe ("mock-server-02") @@ -381,7 +382,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { .cookies( requestCookie("styx_origin_stickyApp", "NA")) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldMatch ("mock-server-0.") @@ -453,7 +454,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .cookies(requestCookie("orc", "appA-02")) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "mock-server-02" @@ -467,7 +468,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .cookies(requestCookie("orc", "appA-0(2|3)")) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldMatch "mock-server-0[23]" @@ -481,7 +482,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .cookies(requestCookie("orc", "(?!)")) .build()) - .wait()!! + .wait() .let { it.status() shouldBe BAD_GATEWAY } @@ -494,7 +495,7 @@ class LoadBalancingGroupSpec : FeatureSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .cookies(requestCookie("orc", "appA-02,appA-0[13]")) .build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldMatch "mock-server-0[123]" diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/PathPrefixRoutingSpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/PathPrefixRoutingSpec.kt index 153c8eeb9..f92f97a8b 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/PathPrefixRoutingSpec.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/PathPrefixRoutingSpec.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.hotels.styx.api.HttpHeaderNames.HOST import com.hotels.styx.api.HttpRequest.get import com.hotels.styx.client.StyxHttpClient import com.hotels.styx.support.StyxServerProvider +import com.hotels.styx.support.blockRequired import com.hotels.styx.support.proxyHttpHostHeader import io.kotest.core.spec.Spec import io.kotest.core.spec.style.StringSpec @@ -36,14 +37,14 @@ class PathPrefixRoutingSpec : StringSpec() { .header(HOST, proxyHost) .build()) .toMono() - .block()!! + .blockRequired() .bodyAs(UTF_8) shouldBe "I'm default" client.send(get("/database/a/path") .header(HOST, proxyHost) .build()) .toMono() - .block()!! + .blockRequired() .bodyAs(UTF_8) shouldBe "I'm database" } } diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/RoutingRestApiSpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/RoutingRestApiSpec.kt index 576709c31..43341b104 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/RoutingRestApiSpec.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/RoutingRestApiSpec.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import com.hotels.styx.api.HttpResponseStatus.OK import com.hotels.styx.client.StyxHttpClient import com.hotels.styx.support.StyxServerProvider import com.hotels.styx.support.adminHostHeader +import com.hotels.styx.support.blockRequired import com.hotels.styx.support.proxyHttpHostHeader import io.kotest.core.spec.Spec import io.kotest.core.spec.style.StringSpec @@ -49,7 +50,7 @@ class RoutingRestApiSpec : StringSpec() { """.trimIndent(), UTF_8) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe CREATED client.send( @@ -62,7 +63,7 @@ class RoutingRestApiSpec : StringSpec() { """.trimIndent(), UTF_8) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe CREATED client.send( @@ -70,9 +71,9 @@ class RoutingRestApiSpec : StringSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .toMono() - .block() + .blockRequired() .let { - it!!.status() shouldBe (OK) + it.status() shouldBe (OK) it.bodyAs(UTF_8) shouldBe ("Responder") } @@ -84,7 +85,7 @@ class RoutingRestApiSpec : StringSpec() { .header(HOST, styxServer().adminHostHeader()) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe OK client.send( @@ -92,7 +93,7 @@ class RoutingRestApiSpec : StringSpec() { .header(HOST, styxServer().proxyHttpHostHeader()) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe NOT_FOUND } @@ -108,7 +109,7 @@ class RoutingRestApiSpec : StringSpec() { """.trimIndent(), UTF_8) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe CREATED client.send( @@ -116,9 +117,9 @@ class RoutingRestApiSpec : StringSpec() { .header(HOST, styxServer().adminHostHeader()) .build()) .toMono() - .block() + .blockRequired() .let { - it!!.status() shouldBe OK + it.status() shouldBe OK it.bodyAs(UTF_8) shouldContain """ responder: type: "StaticResponseHandler" @@ -174,7 +175,7 @@ class RoutingRestApiSpec : StringSpec() { """.trimIndent(), UTF_8) .build()) .toMono() - .block()!! + .blockRequired() .status() shouldBe CREATED } diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/VersionFilesPropertySpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/VersionFilesPropertySpec.kt index 19856a83e..61b39ed60 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/VersionFilesPropertySpec.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/routing/VersionFilesPropertySpec.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import com.hotels.styx.client.StyxHttpClient import com.hotels.styx.support.ResourcePaths.fixturesHome import com.hotels.styx.support.StyxServerProvider import com.hotels.styx.support.adminHostHeader +import com.hotels.styx.support.blockRequired import io.kotest.core.spec.Spec import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe @@ -38,9 +39,9 @@ class VersionFilesPropertySpec : StringSpec() { client.send(request) .toMono() - .block() + .blockRequired() .let { - it!!.status() shouldBe (OK) + it.status() shouldBe (OK) it.bodyAs(UTF_8) shouldBe ("MyFakeVersionText\n") } } diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/server/ServerObjectSpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/server/ServerObjectSpec.kt index 966153541..f862df43c 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/server/ServerObjectSpec.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/server/ServerObjectSpec.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -99,14 +99,14 @@ class ServerObjectSpec : FeatureSpec() { // 2. Send a probe to both of them testClient.send(get("/").header(HOST, "localhost:$httpPort").build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "non-secure" } testClient.secure().send(get("/").header(HOST, "localhost:$httpsPort").build()) - .wait()!! + .wait() .let { it.status() shouldBe OK it.bodyAs(UTF_8) shouldBe "secure" diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/support/StyxServerProvider.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/support/StyxServerProvider.kt index 290dd6ae5..1e7531c3a 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/support/StyxServerProvider.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/support/StyxServerProvider.kt @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2023 Expedia Inc. + Copyright (C) 2013-2026 Expedia Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ import io.micrometer.core.instrument.Clock import io.micrometer.core.instrument.composite.CompositeMeterRegistry import io.micrometer.core.instrument.simple.SimpleMeterRegistry import org.slf4j.LoggerFactory +import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.toMono import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.Path @@ -45,6 +46,8 @@ import java.util.Optional import java.util.concurrent.CompletableFuture import java.util.concurrent.atomic.AtomicReference +fun Mono.blockRequired(): T = block() ?: error("Expected a value but Mono was empty") + val defaultServerConfig = """ proxy: connectors: @@ -145,13 +148,13 @@ fun StyxServerProvider.adminRequest(endpoint: String, debug: Boolean = false): H .build()) .wait(debug = debug) -fun CompletableFuture.wait(debug: Boolean = false) = this.toMono() +fun CompletableFuture.wait(debug: Boolean = false): HttpResponse = this.toMono() .doOnNext { if (debug) { LOGGER.info("${it.status()} - ${it.headers()} - ${it.bodyAs(UTF_8)}") } } - .block() + .block() ?: error("Expected a response but Mono was empty") fun StyxServer.adminHostHeader() = "${this.adminHttpAddress().hostName}:${this.adminHttpAddress().port}" fun StyxServer.proxyHttpHostHeader() = "localhost:${this.proxyHttpAddress().port}" @@ -179,7 +182,7 @@ fun StyxServer.metrics(): Map> { .send(HttpRequest.get("/admin/metrics") .header(HOST, this.adminHostHeader()) .build()) - .wait()!! + .wait() .bodyAs(UTF_8) return flattenMetricsMap(metricsText) as Map> @@ -193,11 +196,11 @@ fun StyxServer.newRoutingObject(name: String, routingObject: String): HttpRespon .build()) .wait() - if (response?.status() != CREATED) { - LOGGER.debug("Object $name was not created. Response from server: ${response?.status()} - '${response?.bodyAs(UTF_8)}'") + if (response.status() != CREATED) { + LOGGER.debug("Object $name was not created. Response from server: ${response.status()} - '${response.bodyAs(UTF_8)}'") } - return response?.status() ?: HttpResponseStatus.statusWithCode(666) + return response.status() } fun StyxServer.removeRoutingObject(name: String): HttpResponseStatus { @@ -207,18 +210,18 @@ fun StyxServer.removeRoutingObject(name: String): HttpResponseStatus { .build()) .wait() - if (response?.status() != OK) { - LOGGER.debug("Object $name was not removed. Response from server: ${response?.status()} - '${response?.bodyAs(UTF_8)}'") + if (response.status() != OK) { + LOGGER.debug("Object $name was not removed. Response from server: ${response.status()} - '${response.bodyAs(UTF_8)}'") } - return response?.status() ?: HttpResponseStatus.statusWithCode(666) + return response.status() } fun StyxServer.routingObject(name: String, debug: Boolean = false): Optional = StyxHttpClient.Builder().build() .send(HttpRequest.get("/admin/routing/objects/$name") .header(HOST, this.adminHostHeader()) .build()) - .wait(debug)!! + .wait(debug) .let { if (it.status() == OK) { Optional.of(it.bodyAs(UTF_8)) @@ -231,7 +234,7 @@ fun StyxServer.routingObjects(debug: Boolean = false): Optional = StyxHt .send(HttpRequest.get("/admin/routing/objects") .header(HOST, this.adminHostHeader()) .build()) - .wait(debug)!! + .wait(debug) .let { if (it.status() == OK) { Optional.of(it.bodyAs(UTF_8)) @@ -243,7 +246,7 @@ fun StyxServer.routingObjects(debug: Boolean = false): Optional = StyxHt fun StyxServer.serverPort(name: String, debug: Boolean = false) = testClient .send(HttpRequest.get("/admin/servers/$name/port") .header(HOST, this.adminHostHeader()).build()) - .wait() + .wait(debug) .bodyAs(UTF_8) .toInt()