diff --git a/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt new file mode 100644 index 0000000..9064ce7 --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt @@ -0,0 +1,157 @@ +package org.prebid.cache.functional + +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.data.forAll +import io.kotest.data.row +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.ktor.client.statement.readText +import io.ktor.http.contentType +import org.mockserver.model.MediaType.APPLICATION_JSON_UTF_8 +import org.mockserver.model.MediaType.APPLICATION_XML +import org.prebid.cache.functional.mapper.objectMapper +import org.prebid.cache.functional.model.request.MediaType.JSON +import org.prebid.cache.functional.model.request.MediaType.XML +import org.prebid.cache.functional.model.request.TransferValue +import org.prebid.cache.functional.service.ApiException +import org.prebid.cache.functional.service.PrebidCacheApi +import org.prebid.cache.functional.testcontainers.ContainerDependencies +import org.prebid.cache.functional.testcontainers.client.WebCacheContainerClient +import org.prebid.cache.functional.testcontainers.container.WebCacheContainer +import org.prebid.cache.functional.util.getRandomString +import org.prebid.cache.functional.util.getRandomUuid +import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR +import org.springframework.http.HttpStatus.NOT_FOUND +import org.springframework.http.HttpStatus.OK + +class ProxyCacheHostSpec : ShouldSpec({ + + lateinit var webCacheContainerClient: WebCacheContainerClient + lateinit var proxyCacheHost: String + lateinit var specPrebidCacheConfig: Map + lateinit var prebidCacheApi: PrebidCacheApi + + beforeSpec { + // given: Mock web cache container is started + ContainerDependencies.webCacheContainer.start() + + // and: Mock web cache client is initialized + webCacheContainerClient = WebCacheContainerClient( + ContainerDependencies.webCacheContainer.host, + ContainerDependencies.webCacheContainer.serverPort + ) + // and: Prebid Cache with allow_external_UUID=true and configured proxy cache host is started + proxyCacheHost = + "${ContainerDependencies.webCacheContainer.getContainerHost()}:${WebCacheContainer.PORT}" + specPrebidCacheConfig = BaseSpec.prebidCacheConfig.getBaseRedisConfig("true") + + BaseSpec.prebidCacheConfig.getProxyCacheHostConfig(proxyCacheHost) + prebidCacheApi = BaseSpec.getPrebidCacheApi(specPrebidCacheConfig) + } + + afterSpec { + // cleanup: Mock web cache container is stopped + ContainerDependencies.webCacheContainer.stop() + + // and: Prebid Cache container is stopped + ContainerDependencies.prebidCacheContainerPool.stopPrebidCacheContainer(specPrebidCacheConfig) + } + + should("throw an exception when proxy cache host doesn't exist") { + // given: Prebid cache config with set up not existing proxy cache host + val cacheHost = getRandomString() + val config = BaseSpec.prebidCacheConfig.getBaseRedisConfig("true") + + BaseSpec.prebidCacheConfig.getProxyCacheHostConfig(cacheHost) + + // when: GET cache endpoint is called with provided cache host + val exception = + shouldThrowExactly { BaseSpec.getPrebidCacheApi(config).getCache(getRandomUuid(), cacheHost) } + + // then: Internal Server Error exception is thrown + assertSoftly { + exception.statusCode shouldBe INTERNAL_SERVER_ERROR.value() + exception.responseBody shouldContain "\"message\":\"$cacheHost: Name or service not known\"" + } + } + + should("throw an exception when proxy cache host returns not OK status code") { + forAll( + row(NOT_FOUND), + row(INTERNAL_SERVER_ERROR) + ) { httpStatus -> + // given: Proxy cache host response is set up to return bad status code and error message + webCacheContainerClient.setProxyCacheHostResponse(httpStatus, httpStatus.name) + + // when: GET cache endpoint is called with provided proxy cache host + val exception = + shouldThrowExactly { prebidCacheApi.getCache(getRandomUuid(), proxyCacheHost) } + + // then: Exception thrown by proxy cache host is returned by PBC + assertSoftly { + exception.statusCode shouldBe httpStatus.value() + exception.responseBody shouldContain httpStatus.name + } + } + } + + should("send a request to proxy cache host when proxy cache host parameter is given on request") { + // given: Proxy cache host response is set up + webCacheContainerClient.setProxyCacheHostResponse(OK, getRandomString()) + + // and: Initial proxy cache host request count is taken + val initialProxyCacheHostRequestCount = webCacheContainerClient.getProxyCacheHostRecordedRequestCount() + + // when: GET cache endpoint is called with provided proxy cache host + val requestUuid = getRandomUuid() + prebidCacheApi.getCache(requestUuid, proxyCacheHost) + + // then: PBC called proxy cache host + webCacheContainerClient.getProxyCacheHostRecordedRequestCount() shouldBe initialProxyCacheHostRequestCount + 1 + + val proxyCacheHostRequest = webCacheContainerClient.getProxyCacheHostRecordedRequests()!!.last() + proxyCacheHostRequest.queryStringParameters?.containsEntry("uuid", requestUuid) + } + + should("return a response body as a plain String requested from proxy cache host") { + // given: Proxy cache host response is set up to return a plain String response body + val cacheHostResponseBody = getRandomString() + webCacheContainerClient.setProxyCacheHostResponse(OK, cacheHostResponseBody) + + // when: GET cache endpoint is called with provided proxy cache host + val response = prebidCacheApi.getCache(getRandomUuid(), proxyCacheHost) + + // then: PBC response body should be equal to proxy cache host response body + response.readText() shouldBe cacheHostResponseBody + } + + should("return a response body as a JSON or XML object requested from proxy cache host") { + forAll( + row(TransferValue.getDefaultJsonValue(), APPLICATION_JSON_UTF_8, JSON), + row(TransferValue.getDefaultXmlValue(), APPLICATION_XML, XML) + ) { proxyCacheResponseBody, proxyCacheResponseMediaType, prebidCacheResponseMediaType -> + // given: Proxy cache host response is set up to return a JSON response body + webCacheContainerClient.setProxyCacheHostResponse( + OK, + objectMapper.writeValueAsString(proxyCacheResponseBody), + proxyCacheResponseMediaType + ) + + // when: GET cache endpoint is called with provided proxy cache host + val response = prebidCacheApi.getCache(getRandomUuid(), proxyCacheHost) + + // then: PBC response body should be equal to proxy cache host response body + response.contentType()?.contentType shouldBe "application" + response.contentType()?.contentSubtype shouldBe prebidCacheResponseMediaType.getValue() + + // and: transfer value is returned + val responseTransferValue = objectMapper.readValue(response.readText(), TransferValue::class.java) + + assertSoftly { + responseTransferValue.adm shouldBe proxyCacheResponseBody.adm + responseTransferValue.width shouldBe proxyCacheResponseBody.width + responseTransferValue.height shouldBe proxyCacheResponseBody.height + } + } + } +}) diff --git a/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt index 4474dd5..12c3658 100644 --- a/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt @@ -26,7 +26,7 @@ class SecondaryCacheSpec : ShouldSpec({ webCacheContainerClient = WebCacheContainerClient( ContainerDependencies.webCacheContainer.host, ContainerDependencies.webCacheContainer.serverPort - ) + ).apply { initSecondaryCacheResponse() } webCacheContainerUri = "http://${ContainerDependencies.webCacheContainer.getContainerHost()}:${WebCacheContainer.PORT}" @@ -57,7 +57,8 @@ class SecondaryCacheSpec : ShouldSpec({ responseObject.responses[0].uuid shouldBe requestObject.puts[0].key // and: Request to secondary cache was sent - val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + val secondaryCacheRecordedRequests = + webCacheContainerClient.getSecondaryCacheRecordedRequests(requestObject.puts[0].key!!) secondaryCacheRecordedRequests?.size shouldBe 1 // and: Request contained secondaryCache=yes query parameter @@ -84,7 +85,8 @@ class SecondaryCacheSpec : ShouldSpec({ responseObject.responses[0].uuid shouldBe requestObject.puts[0].key // and: Request to secondary cache was sent - val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + val secondaryCacheRecordedRequests = + webCacheContainerClient.getSecondaryCacheRecordedRequests(requestObject.puts[0].key!!) secondaryCacheRecordedRequests?.size shouldBe 1 // and: Secondary cache request 'expiry' parameter matches to the PBC request 'ttlseconds' parameter @@ -109,7 +111,8 @@ class SecondaryCacheSpec : ShouldSpec({ responseObject.responses[0].uuid shouldBe requestObject.puts[0].key // and: Request to secondary cache was sent - val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + val secondaryCacheRecordedRequests = + webCacheContainerClient.getSecondaryCacheRecordedRequests(requestObject.puts[0].key!!) secondaryCacheRecordedRequests?.size shouldBe 1 // and: Secondary cache request 'expiry' parameter matches to the Prebid Cache 'cache.expiry.sec' config property @@ -137,7 +140,8 @@ class SecondaryCacheSpec : ShouldSpec({ responseObject.responses[0].uuid shouldBe requestObject.puts[0].key // and: Request to secondary cache was sent - val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + val secondaryCacheRecordedRequests = + webCacheContainerClient.getSecondaryCacheRecordedRequests(requestObject.puts[0].key!!) secondaryCacheRecordedRequests?.size shouldBe 1 // and: Secondary cache request 'expiry' parameter matches to the Prebid Cache 'cache.max.expiry' config property @@ -165,7 +169,8 @@ class SecondaryCacheSpec : ShouldSpec({ responseObject.responses[0].uuid shouldBe requestObject.puts[0].key // and: Request to secondary cache was sent - val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + val secondaryCacheRecordedRequests = + webCacheContainerClient.getSecondaryCacheRecordedRequests(requestObject.puts[0].key!!) secondaryCacheRecordedRequests?.size shouldBe 1 // and: Secondary cache request 'expiry' parameter matches to the Prebid Cache 'cache.min.expiry' config property diff --git a/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt index 0146f6e..6ca9606 100644 --- a/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt +++ b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt @@ -45,9 +45,10 @@ class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) { } } - suspend fun getCache(uuid: String?): HttpResponse = + suspend fun getCache(uuid: String?, proxyCacheHost: String? = null): HttpResponse = client.get(CACHE_ENDPOINT) { if (uuid != null) parameter(UUID_QUERY_PARAMETER, uuid) + if (proxyCacheHost != null) parameter(PROXY_CACHE_HOST_QUERY_PARAMETER, proxyCacheHost) } suspend fun postCache(requestObject: RequestObject, secondaryCache: String? = null): ResponseObject = @@ -64,6 +65,7 @@ class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) { companion object { private const val CACHE_ENDPOINT = "/cache" private const val UUID_QUERY_PARAMETER = "uuid" + private const val PROXY_CACHE_HOST_QUERY_PARAMETER = "ch" private const val SECONDARY_CACHE_QUERY_PARAMETER = "secondaryCache" } } diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt index 459d684..1d2c919 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt @@ -51,6 +51,12 @@ class PrebidCacheContainerConfig(private val redisHost: String, private val aero "cache.secondary-uris" to secondaryCacheUri ) + fun getProxyCacheHostConfig(cacheHost: String): Map = + mapOf( + "cache.host_param_protocol" to "http", + "cache.allowed-proxy-host" to cacheHost + ) + private fun getBaseConfig(allowExternalUuid: String): Map = getCachePrefixConfig() + getCacheExpiryConfig() + getAllowExternalUuidConfig(allowExternalUuid) + getCacheTimeoutConfig("500") diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/client/WebCacheContainerClient.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/client/WebCacheContainerClient.kt index c56ea58..39c2e45 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/client/WebCacheContainerClient.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/client/WebCacheContainerClient.kt @@ -1,13 +1,18 @@ package org.prebid.cache.functional.testcontainers.client import org.mockserver.client.MockServerClient +import org.mockserver.matchers.Times import org.mockserver.mock.Expectation import org.mockserver.model.HttpRequest import org.mockserver.model.HttpRequest.request import org.mockserver.model.HttpResponse.response import org.mockserver.model.JsonPathBody.jsonPath +import org.mockserver.model.MediaType +import org.mockserver.model.MediaType.APPLICATION_JSON_UTF_8 import org.prebid.cache.functional.testcontainers.container.WebCacheContainer.Companion.WEB_CACHE_PATH +import org.springframework.http.HttpMethod.GET import org.springframework.http.HttpMethod.POST +import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus.OK class WebCacheContainerClient(mockServerHost: String, mockServerPort: Int) { @@ -16,12 +21,36 @@ class WebCacheContainerClient(mockServerHost: String, mockServerPort: Int) { init { mockServerClient = MockServerClient(mockServerHost, mockServerPort) - initResponse() } - fun getRecordedRequests(uuidKey: String): Array? = + fun getProxyCacheHostRecordedRequests(): Array? = + mockServerClient.retrieveRecordedRequests(getProxyCacheHostRequest()) + + fun getProxyCacheHostRecordedRequestCount(): Int = + getProxyCacheHostRecordedRequests()?.size ?: 0 + + fun setProxyCacheHostResponse( + httpStatus: HttpStatus = OK, + body: String = "", + mediaType: MediaType = APPLICATION_JSON_UTF_8 + ): Array? = + mockServerClient.`when`(getProxyCacheHostRequest(), Times.exactly(1)) + .respond( + response().withStatusCode(httpStatus.value()) + .withBody(body, mediaType) + ) + + fun getSecondaryCacheRecordedRequests(uuidKey: String): Array? = mockServerClient.retrieveRecordedRequests(getSecondaryCacheRequest(uuidKey)) + fun initSecondaryCacheResponse(): Array? = + mockServerClient.`when`(getSecondaryCacheRequest()) + .respond(response().withStatusCode(OK.value())) + + private fun getProxyCacheHostRequest(): HttpRequest = + request().withMethod(GET.name) + .withPath("/$WEB_CACHE_PATH") + private fun getSecondaryCacheRequest(): HttpRequest = request().withMethod(POST.name) .withPath("/$WEB_CACHE_PATH") @@ -30,8 +59,4 @@ class WebCacheContainerClient(mockServerHost: String, mockServerPort: Int) { request().withMethod(POST.name) .withPath("/$WEB_CACHE_PATH") .withBody(jsonPath("\$.puts[?(@.key == '$uuidKey')]")) - - private fun initResponse(): Array? = - mockServerClient.`when`(getSecondaryCacheRequest()) - .respond(response().withStatusCode(OK.value())) }