From 0b96933484c02ba8d6ec9e499ec090d23d97f10e Mon Sep 17 00:00:00 2001 From: mhupalo Date: Mon, 21 Mar 2022 12:50:10 +0200 Subject: [PATCH 01/11] Add functional test framework in Kotlin --- Dockerfile | 10 + pom.xml | 157 +++++++++++++++- src/main/docker/run.sh | 5 + .../cache/functional/AerospikeCacheSpec.kt | 140 ++++++++++++++ .../org/prebid/cache/functional/BaseSpec.kt | 19 ++ .../cache/functional/GeneralCacheSpec.kt | 173 ++++++++++++++++++ .../prebid/cache/functional/RedisCacheSpec.kt | 123 +++++++++++++ .../cache/functional/SecondaryCacheSpec.kt | 167 +++++++++++++++++ .../functional/model/request/MediaType.kt | 13 ++ .../model/request/PayloadTransfer.kt | 32 ++++ .../functional/model/request/RequestObject.kt | 14 ++ .../functional/model/request/TransferValue.kt | 16 ++ .../model/response/ResponseObject.kt | 3 + .../functional/model/response/ResponseUuid.kt | 3 + .../cache/functional/service/ApiException.kt | 3 + .../functional/service/PrebidCacheApi.kt | 53 ++++++ .../testcontainers/ContainerDependencies.kt | 33 ++++ .../testcontainers/ContainerStartable.kt | 16 ++ .../PrebidCacheContainerConfig.kt | 73 ++++++++ .../PrebidCacheContainerPool.kt | 33 ++++ .../client/WebCacheContainerClient.kt | 38 ++++ .../container/AerospikeContainer.kt | 23 +++ .../container/PrebidCacheContainer.kt | 58 ++++++ .../container/RedisContainer.kt | 14 ++ .../container/WebCacheContainer.kt | 15 ++ .../cache/functional/util/PrebidCacheUtil.kt | 22 +++ 26 files changed, 1254 insertions(+), 2 deletions(-) create mode 100644 Dockerfile create mode 100755 src/main/docker/run.sh create mode 100644 src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/model/request/TransferValue.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/model/response/ResponseObject.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/model/response/ResponseUuid.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/service/ApiException.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerStartable.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/testcontainers/client/WebCacheContainerClient.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/testcontainers/container/AerospikeContainer.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/testcontainers/container/RedisContainer.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/testcontainers/container/WebCacheContainer.kt create mode 100644 src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ed2f22c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM openjdk:11-jre-slim + +WORKDIR /app/prebid-cache + +COPY src/main/docker/run.sh ./ +COPY target/prebid-cache.jar ./prebid-cache.jar + +EXPOSE 8080 + +ENTRYPOINT [ "/app/prebid-cache/run.sh" ] diff --git a/pom.xml b/pom.xml index 5881454..2353059 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 org.prebid.cache @@ -22,10 +23,14 @@ org.springframework.boot spring-boot-starter-parent 2.1.6.RELEASE - + + false + false + false + src/main/resources org.prebid.cache.PBCacheApplication 5.3.1 @@ -66,6 +71,15 @@ 0.13.2 2.17.1 2.5.3 + + + 5.1.0 + 1.6.10 + 1.16.3 + 5.12.0 + 3.3.0 + 2.9.10 + 0.39.1 @@ -213,6 +227,63 @@ ${awaitility.version} test + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + test + + + io.kotest + kotest-runner-junit5-jvm + ${kotest.version} + test + + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + org.testcontainers + mockserver + ${testcontainers.version} + test + + + org.mock-server + mockserver-client-java + ${mockserver-client.version} + test + + + io.rest-assured + rest-assured + ${restassured.version} + test + + + io.rest-assured + json-path + ${restassured.version} + test + + + io.rest-assured + xml-path + ${restassured.version} + test + + + com.fasterxml.jackson.module + jackson-module-kotlin + ${jackson-kotlin-module.version} + test + + @@ -266,6 +337,9 @@ org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} + + ${skipUnitTests} + org.apache.maven.plugins @@ -292,6 +366,84 @@ src/scripts/custom_launch.script + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-surefire-plugin.version} + + ${skipFunctionalTests} + + **/*Spec.java + + + ${useFixedContainerPorts} + + + + + + integration-test + verify + + + + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + test-compile + process-test-sources + + test-compile + + + + src/test/kotlin + + + + + + + io.fabric8 + docker-maven-plugin + ${docker-maven-plugin.version} + + ${skipFunctionalTests} + + + ${project.artifactId} + + ${project.basedir}/Dockerfile + + latest + + + + + + + + build-containers + pre-integration-test + + build + + + + remove-containers + post-integration-test + + remove + + + + + org.apache.maven.plugins maven-checkstyle-plugin @@ -309,6 +461,7 @@ jacoco-maven-plugin ${jacoco-plugin.version} + ${skipUnitTests} **/listeners/** **/repository/** diff --git a/src/main/docker/run.sh b/src/main/docker/run.sh new file mode 100755 index 0000000..69677ec --- /dev/null +++ b/src/main/docker/run.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +exec java \ + -jar \ + /app/prebid-cache/prebid-cache.jar diff --git a/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt new file mode 100644 index 0000000..6f6ebf7 --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt @@ -0,0 +1,140 @@ +package org.prebid.cache.functional + +import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import org.prebid.cache.functional.BaseSpec.Companion.prebidCacheConfig +import org.prebid.cache.functional.model.request.PayloadTransfer +import org.prebid.cache.functional.model.request.RequestObject +import org.prebid.cache.functional.model.request.TransferValue +import org.prebid.cache.functional.model.response.ResponseObject +import org.prebid.cache.functional.service.ApiException +import org.prebid.cache.functional.testcontainers.ContainerDependencies +import org.prebid.cache.functional.util.PrebidCacheUtil + +class AerospikeCacheSpec : ShouldSpec({ + + should("throw an exception when cache record is absent in Aerospike repository") { + // given: Prebid cache config + val config = prebidCacheConfig.getBaseAerospikeConfig("true") + val cachePrefix = config["cache.prefix"] + + // when: GET cache endpoint with random UUID is called + val randomUuid = PrebidCacheUtil.getRandomUuid() + val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi(config).getCache(randomUuid) } + + // then: Not Found exception is thrown + exception.statusCode shouldBe 404 + exception.responseBody shouldContain "\"message\":\"Resource Not Found: uuid $cachePrefix$randomUuid\"" + } + + should("rethrow an exception from Aerospike cache server when such happens") { + // given: Prebid Cache with not matched to Aerospike server namespace + val unmatchedNamespace = PrebidCacheUtil.getRandomString() + val config = prebidCacheConfig.getCacheExpiryConfig() + prebidCacheConfig.getAerospikeConfig(unmatchedNamespace) + val prebidCacheApi = BaseSpec.getPrebidCacheApi(config) + + // and: Default request object + val requestObject = RequestObject.getDefaultJsonRequestObject() + + // when: POST cache endpoint is called + val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } + + // then: Internal Server Error exception is thrown + exception.statusCode shouldBe 500 + exception.responseBody shouldContain "Namespace not found in partition map: $unmatchedNamespace" + + // cleanup + ContainerDependencies.prebidCacheContainerPool.stopPrebidCacheContainer(config) + } + + should("throw an exception when aerospike.prevent_UUID_duplication=true and request with already existing UUID is send") { + // given: Prebid Cache with aerospike.prevent_UUID_duplication=true property + val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseAerospikeConfig("true") + + prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("true")) + + // and: First request object with set UUID + val uuid = PrebidCacheUtil.getRandomUuid() + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() + xmlPayloadTransfer.key = uuid + val requestObject = RequestObject(listOf(xmlPayloadTransfer)) + + // and: First request object is saved to Aerospike cache + prebidCacheApi.postCache(requestObject) + + // and: Second request object with already existing UUID is prepared + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() + jsonPayloadTransfer.key = uuid + val secondRequestObject = RequestObject(listOf(jsonPayloadTransfer)) + + // when: POST cache endpoint is called for the second time + val exception = shouldThrowExactly { prebidCacheApi.postCache(secondRequestObject) } + + // then: Bad Request exception is thrown + exception.statusCode shouldBe 400 + exception.responseBody shouldContain "\"message\":\"UUID duplication.\"" + } + + should("return back two request UUIDs when allow_external_UUID=true and 2 payload transfers were successfully cached in Aerospike") { + // given: Prebid Cache with allow_external_UUID=true property + val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseAerospikeConfig("true") + + prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false")) + + // and: Request object with 2 payload transfers and set UUIDs is prepared + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() + xmlPayloadTransfer.key = PrebidCacheUtil.getRandomUuid() + jsonPayloadTransfer.key = PrebidCacheUtil.getRandomUuid() + val requestObject = RequestObject(listOf(xmlPayloadTransfer, jsonPayloadTransfer)) + + // when: POST cache endpoint is called + val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject) + + // then: UUIDs from request object are returned + responseObject.responses.isEmpty() shouldBe false + responseObject.responses.size shouldBe 2 + + val responseUuidList = listOf(responseObject.responses[0].uuid, responseObject.responses[1].uuid) + responseUuidList shouldContain requestObject.puts[0].key + responseUuidList shouldContain requestObject.puts[1].key + } + + should("update existing cache record when aerospike.prevent_UUID_duplication=false and request with already existing UUID is send") { + // given: Prebid Cache with aerospike.prevent_UUID_duplication=false + val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseAerospikeConfig("true") + + prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false")) + + // and: First request object + val uuid = PrebidCacheUtil.getRandomUuid() + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() + xmlPayloadTransfer.key = uuid + val requestObject = RequestObject(listOf(xmlPayloadTransfer)) + + // and: First request object is saved to Aerospike cache + prebidCacheApi.postCache(requestObject) + + // and: Second request object with already existing UUID is prepared + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() + jsonPayloadTransfer.key = uuid + val secondRequestObject = RequestObject(listOf(jsonPayloadTransfer)) + val requestTransferValue = PrebidCacheUtil.objectMapper.readValue(secondRequestObject.puts[0].value, TransferValue::class.java) + + // when: POST cache endpoint is called for the second time + val responseObject = prebidCacheApi.postCache(secondRequestObject) + + // then: UUID from request is returned + responseObject.responses.isEmpty() shouldBe false + responseObject.responses.size shouldBe 1 + responseObject.responses[0].uuid shouldBe secondRequestObject.puts[0].key + + // and: Cache record was updated in Aerospike with a second request object payload + val getCacheResponse = prebidCacheApi.getCache(responseObject.responses[0].uuid) + val responseTransferValue = getCacheResponse.`as`(TransferValue::class.java) + + responseTransferValue.adm shouldBe requestTransferValue.adm + responseTransferValue.width shouldBe requestTransferValue.width + responseTransferValue.height shouldBe requestTransferValue.height + } +}) diff --git a/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt b/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt new file mode 100644 index 0000000..71d7aab --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt @@ -0,0 +1,19 @@ +package org.prebid.cache.functional + +import org.prebid.cache.functional.service.PrebidCacheApi +import org.prebid.cache.functional.testcontainers.ContainerDependencies +import org.prebid.cache.functional.testcontainers.PrebidCacheContainerConfig + +abstract class BaseSpec { + + companion object { + val prebidCacheConfig = PrebidCacheContainerConfig(ContainerDependencies.redisContainer.getContainerHost(), + ContainerDependencies.aerospikeContainer.getContainerHost()) + + fun getPrebidCacheApi(config: Map = prebidCacheConfig.getBaseRedisConfig("false")): PrebidCacheApi { + val prebidCacheContainer = + ContainerDependencies.prebidCacheContainerPool.getPrebidCacheContainer(config) + return PrebidCacheApi(prebidCacheContainer.getHostUri()) + } + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt new file mode 100644 index 0000000..77ecea4 --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt @@ -0,0 +1,173 @@ +package org.prebid.cache.functional + +import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import org.prebid.cache.functional.model.request.MediaType.UNSUPPORTED +import org.prebid.cache.functional.model.request.RequestObject +import org.prebid.cache.functional.model.request.TransferValue +import org.prebid.cache.functional.service.ApiException +import org.prebid.cache.functional.util.PrebidCacheUtil + +class GeneralCacheSpec : ShouldSpec({ + + should("throw an exception when 'uuid' query parameter is not present in request") { + // when: GET cache endpoint is called without 'uuid' query parameter + val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi().getCache(null) } + + // then: Bad Request exception is thrown + exception.statusCode shouldBe 400 + exception.responseBody shouldContain "\"message\":\"Invalid Parameter(s): uuid not found.\"" + } + + should("throw an exception when payload transfer key is given and allow_external_UUID=false") { + // given: Request object with set payload transfer key + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + + // when: POST cache endpoint is called + val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi().postCache(requestObject) } + + // then: Bad Request exception is thrown + exception.statusCode shouldBe 400 + exception.responseBody shouldContain "\"message\":\"Prebid cache host forbids specifying UUID in request.\"" + } + + should("throw an exception when allow_external_UUID=true and payload transfer key not in UUID format is given") { + // given: Prebid Cache with allow_external_UUID=true property + val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("true")) + + // and: Request object with set payload transfer key not in UUID format + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + "*" + + // when: POST cache endpoint is called + val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } + + // then: Bad Request exception is thrown + exception.statusCode shouldBe 400 + exception.responseBody shouldContain "\"message\":\"Invalid UUID: [${requestObject.puts[0].key}].\"" + } + + should("throw an exception when allow_external_UUID=true and empty payload transfer key is given") { + // given: Prebid Cache with allow_external_UUID=true property + val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("true")) + + // and: Request object with set empty payload transfer key + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].key = "" + + // when: POST cache endpoint is called + val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } + + // then: Bad Request exception is thrown + exception.statusCode shouldBe 400 + exception.responseBody shouldContain "\"message\":\"Invalid UUID: [].\"" + } + + should("throw an exception when payload object with unsupported media type is fetched from repository") { + // given: Request object with set unsupported media type + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].type = UNSUPPORTED + + // and: POST cache endpoint is called + val response = BaseSpec.getPrebidCacheApi().postCache(requestObject) + + // when: GET cache endpoint is called + val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi().getCache(response.responses[0].uuid) } + + // then: Unsupported Media Type exception is thrown + exception.statusCode shouldBe 415 + exception.responseBody shouldContain "\"message\":\"Unsupported Media Type.\"" + } + + should("throw an exception on POST when request processing takes > than 'cache.timeout.ms' config property") { + // given: Prebid Cache with a low cache request processing timeout ms + val requestTimeoutMs = "1" + val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("false") + + BaseSpec.prebidCacheConfig.getCacheTimeoutConfig(requestTimeoutMs)) + + // and: Request object + val requestObject = RequestObject.getDefaultJsonRequestObject() + + // and: POST cache endpoint is called + val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } + + // then: Internal Server Exception is thrown + exception.statusCode shouldBe 500 + exception.responseBody shouldContain "\"message\":\"Did not observe any item or terminal signal within " + + "${requestTimeoutMs}ms in 'circuitBreaker' (and no fallback has been configured)\"" + } + + should("throw an exception on GET when request processing takes > than 'cache.timeout.ms' config property") { + // given: Prebid Cache with a low cache request processing timeout ms + val requestTimeoutMs = "1" + val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("false") + + BaseSpec.prebidCacheConfig.getCacheTimeoutConfig(requestTimeoutMs)) + + // and: GET cache endpoint is called + val exception = shouldThrowExactly { prebidCacheApi.getCache(PrebidCacheUtil.getRandomUuid()) } + + // then: Internal Server Exception is thrown + exception.statusCode shouldBe 500 + exception.responseBody shouldContain "\"message\":\"Did not observe any item or terminal signal within " + + "${requestTimeoutMs}ms in 'circuitBreaker' (and no fallback has been configured)\"" + } + + should("return the same JSON transfer value which was saved to cache") { + // given: Request object with JSON transfer value + val requestObject = RequestObject.getDefaultJsonRequestObject() + val requestTransferValue = PrebidCacheUtil.objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java) + + // and: POST cache endpoint is called + val postResponse = BaseSpec.getPrebidCacheApi().postCache(requestObject) + + // when: GET cache endpoint is called + val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid) + + // then: transfer value is returned + getCacheResponse.contentType shouldBe "application/${requestObject.puts[0].type.getValue()};charset=UTF-8" + + val responseTransferValue = getCacheResponse.`as`(TransferValue::class.java) + responseTransferValue.adm shouldBe requestTransferValue.adm + responseTransferValue.width shouldBe requestTransferValue.width + responseTransferValue.height shouldBe requestTransferValue.height + } + + should("return the same XML transfer value which was saved to cache") { + // given: Request object with XML transfer value + val requestObject = RequestObject.getDefaultXmlRequestObject() + val requestTransferValue = PrebidCacheUtil.objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java) + + // and: POST cache endpoint is called + val postResponse = BaseSpec.getPrebidCacheApi().postCache(requestObject) + + // when: GET cache endpoint is called + val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid) + + // then: transfer value is returned + getCacheResponse.contentType shouldBe "application/${requestObject.puts[0].type.getValue()}" + + val responseTransferValue = + PrebidCacheUtil.objectMapper.readValue(getCacheResponse.body.asString(), TransferValue::class.java) + responseTransferValue.adm shouldBe requestTransferValue.adm + responseTransferValue.width shouldBe requestTransferValue.width + responseTransferValue.height shouldBe requestTransferValue.height + } + + should("return the same String transfer value which was saved to cache") { + // given: Request object with set transfer value as plain String + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].value = PrebidCacheUtil.getRandomString() + + // and: POST cache endpoint is called + val postResponse = BaseSpec.getPrebidCacheApi().postCache(requestObject) + + // when: GET cache endpoint is called + val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid) + + // then: transfer value as a plain String is returned + getCacheResponse.body.asString() shouldBe requestObject.puts[0].value + } +}) diff --git a/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt new file mode 100644 index 0000000..f3b0a6c --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt @@ -0,0 +1,123 @@ +package org.prebid.cache.functional + +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import org.prebid.cache.functional.BaseSpec.Companion.prebidCacheConfig +import org.prebid.cache.functional.model.request.PayloadTransfer +import org.prebid.cache.functional.model.request.RequestObject +import org.prebid.cache.functional.model.response.ResponseObject +import org.prebid.cache.functional.service.ApiException +import org.prebid.cache.functional.testcontainers.ContainerDependencies +import org.prebid.cache.functional.util.PrebidCacheUtil +import java.util.* + +class RedisCacheSpec : ShouldSpec({ + + should("throw an exception when cache record is absent in Redis repository") { + // given: Prebid cache config + val config = prebidCacheConfig.getBaseRedisConfig("true") + val cachePrefix = config["cache.prefix"] + + // when: GET cache endpoint with random UUID is called + val randomUuid = PrebidCacheUtil.getRandomUuid() + val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi(config).getCache(randomUuid) } + + // then: Not Found exception is thrown + exception.statusCode shouldBe 404 + exception.responseBody shouldContain "\"message\":\"Resource Not Found: uuid $cachePrefix$randomUuid\"" + } + + should("rethrow an exception from Redis cache server when such happens") { + // given: Prebid Cache with set min and max expiry as 0 + val config = prebidCacheConfig.getBaseRedisConfig("false") + prebidCacheConfig.getCacheExpiryConfig("0", "0") + val prebidCacheApi = BaseSpec.getPrebidCacheApi(config) + + // and: Request object + val requestObject = RequestObject.getDefaultJsonRequestObject() + + // when: POST cache endpoint is called + val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } + + // then: Internal Server Error exception is thrown + exception.statusCode shouldBe 500 + exception.responseBody shouldContain "\"message\":\"ERR invalid expire time in setex\"" + + // cleanup + ContainerDependencies.prebidCacheContainerPool.stopPrebidCacheContainer(config) + } + + should("return back PBC UUID when allow_external_UUID=false and request object was successfully cached in Redis") { + // given: Request object + val requestObject = RequestObject.getDefaultJsonRequestObject() + + // when: POST cache endpoint is called + val responseObject: ResponseObject = BaseSpec.getPrebidCacheApi().postCache(requestObject) + + // then: Response contains 1 UUID generated by Prebid Cache + responseObject.responses.isEmpty() shouldBe false + responseObject.responses.size shouldBe 1 + shouldNotThrowAny { UUID.fromString(responseObject.responses[0].uuid) } + } + + should("return back two random UUIDs when allow_external_UUID=false and 2 payload transfers were successfully cached") { + // given: Request object + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() + xmlPayloadTransfer.key = null + jsonPayloadTransfer.key = null + val requestObject = RequestObject(listOf(xmlPayloadTransfer, jsonPayloadTransfer)) + + // when: POST cache endpoint is called + val responseObject: ResponseObject = BaseSpec.getPrebidCacheApi().postCache(requestObject) + + // then: Response contains 2 UUIDs generated by Prebid Cache + responseObject.responses.isEmpty() shouldBe false + responseObject.responses.size shouldBe 2 + shouldNotThrowAny { UUID.fromString(responseObject.responses[0].uuid) } + shouldNotThrowAny { UUID.fromString(responseObject.responses[1].uuid) } + } + + should("return back a request UUID when allow_external_UUID=true and request object was successfully cached") { + // given: Prebid Cache with enabled allow_external_UUID property + val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig("true")) + + // and: Request object with set payload transfer UUID key + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + + // when: POST cache endpoint is called + val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject) + + // then: UUID from request is returned + responseObject.responses.isEmpty() shouldBe false + responseObject.responses.size shouldBe 1 + responseObject.responses[0].uuid shouldBe requestObject.puts[0].key + } + + should("return back two request UUIDs when allow_external_UUID=true and 2 payload transfers were successfully cached in Redis") { + // given: Prebid Cache with enabled allow_external_UUID property + val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig("true")) + + // and: Request object with set 2 payload transfers + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() + xmlPayloadTransfer.key = PrebidCacheUtil.getRandomUuid() + jsonPayloadTransfer.key = PrebidCacheUtil.getRandomUuid() + val requestObject = RequestObject(listOf(xmlPayloadTransfer, jsonPayloadTransfer)) + + // when: POST cache endpoint is called + val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject) + + // then: UUIDs from request are returned + responseObject.responses.isEmpty() shouldBe false + responseObject.responses.size shouldBe 2 + + val responseUuidList = listOf(responseObject.responses[0].uuid, responseObject.responses[1].uuid) + responseUuidList shouldContain requestObject.puts[0].key + responseUuidList shouldContain requestObject.puts[1].key + } +}) diff --git a/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt new file mode 100644 index 0000000..fab773e --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt @@ -0,0 +1,167 @@ +package org.prebid.cache.functional + +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import org.prebid.cache.functional.model.request.RequestObject +import org.prebid.cache.functional.model.response.ResponseObject +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.PrebidCacheUtil + +class SecondaryCacheSpec : ShouldSpec({ + + lateinit var webCacheContainerClient: WebCacheContainerClient + lateinit var webCacheContainerUri: 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) + webCacheContainerUri = "http://${ContainerDependencies.webCacheContainer.getContainerHost()}:${WebCacheContainer.PORT}" + + // and: Prebid Cache with allow_external_UUID=true and configured secondary cache is started + specPrebidCacheConfig = BaseSpec.prebidCacheConfig.getBaseAerospikeConfig("true") + + BaseSpec.prebidCacheConfig.getSecondaryCacheConfig(webCacheContainerUri) + 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("send a request to secondary cache when secondary cache is configures and secondaryCache query parameter is given on request") { + // given: Request object with set payload UUID key + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + + // when: POST cache endpoint is called + val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") + + // then: UUID from request is returned + responseObject.responses.size shouldBe 1 + responseObject.responses[0].uuid shouldBe requestObject.puts[0].key + + // and: Request to secondary cache was sent + val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + secondaryCacheRecordedRequests?.size shouldBe 1 + + // and: Request contained secondaryCache=yes query parameter + secondaryCacheRecordedRequests?.first()?.queryStringParameters?.containsEntry("secondaryCache", "yes") + + // and: Secondary cache request body matched to the Prebid Cache request object + val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + secondaryCacheRequest shouldBe requestObject + } + + should("set cache expiry equals to request 'ttlseconds' when ttlseconds parameter is given") { + // given: Request object with set 'ttlseconds' parameter + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].ttlseconds = 400 + requestObject.puts[0].expiry = 300 + + // when: POST cache endpoint is called + val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") + + // then: UUID from request is returned + responseObject.responses.size shouldBe 1 + responseObject.responses[0].uuid shouldBe requestObject.puts[0].key + + // and: Request to secondary cache was sent + val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + secondaryCacheRecordedRequests?.size shouldBe 1 + + // and: Secondary cache request 'expiry' parameter matches to the PBC request 'ttlseconds' parameter + val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + secondaryCacheRequest.puts.size shouldBe 1 + secondaryCacheRequest.puts[0].expiry shouldBe requestObject.puts[0].ttlseconds + } + + should("set cache expiry from 'cache.expiry.sec' configuration property when request 'ttlseconds' and 'expiry' are absent'") { + // given: Request object with absent 'ttlseconds' and 'expiry' + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].ttlseconds = null + requestObject.puts[0].expiry = null + + // when: POST cache endpoint is called + val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") + + // then: UUID from request is returned + responseObject.responses.size shouldBe 1 + responseObject.responses[0].uuid shouldBe requestObject.puts[0].key + + // and: Request to secondary cache was sent + val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + secondaryCacheRecordedRequests?.size shouldBe 1 + + // and: Secondary cache request 'expiry' parameter matches to the Prebid Cache 'cache.expiry.sec' config property + val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + secondaryCacheRequest.puts.size shouldBe 1 + secondaryCacheRequest.puts[0].expiry shouldBe specPrebidCacheConfig["cache.expiry.sec"]?.toLong() + } + + should("set cache expiry from 'cache.max.expiry' configuration property when request expiry > config max expiry") { + // given: Prebid Cache configuration 'cache.max.expiry' property value + val configCacheMaxExpiry = specPrebidCacheConfig["cache.max.expiry"]?.toLong() + + // and: Request object with set 'expiry' higher than configuration 'cache.max.expiry' + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].ttlseconds = null + requestObject.puts[0].expiry = configCacheMaxExpiry!! + 1 + + // when: POST cache endpoint is called + val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") + + // then: UUID from request is returned + responseObject.responses.size shouldBe 1 + responseObject.responses[0].uuid shouldBe requestObject.puts[0].key + + // and: Request to secondary cache was sent + val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + secondaryCacheRecordedRequests?.size shouldBe 1 + + // and: Secondary cache request 'expiry' parameter matches to the Prebid Cache 'cache.max.expiry' config property + val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + secondaryCacheRequest.puts.size shouldBe 1 + secondaryCacheRequest.puts[0].expiry shouldBe configCacheMaxExpiry + } + + should("set cache expiry from 'cache.min.expiry' configuration property when request expiry < config min expiry") { + // given: Prebid Cache configuration 'cache.min.expiry' property value + val configCacheMinExpiry = specPrebidCacheConfig["cache.min.expiry"]?.toLong() + + // and: Request object with set 'expiry' lower than configuration 'cache.min.expiry' + val requestObject = RequestObject.getDefaultJsonRequestObject() + requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].ttlseconds = null + requestObject.puts[0].expiry = configCacheMinExpiry!! - 1 + + // when: POST cache endpoint is called + val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") + + // then: UUID from request is returned + responseObject.responses.size shouldBe 1 + responseObject.responses[0].uuid shouldBe requestObject.puts[0].key + + // and: Request to secondary cache was sent + val secondaryCacheRecordedRequests = webCacheContainerClient.getRecordedRequests(requestObject.puts[0].key!!) + secondaryCacheRecordedRequests?.size shouldBe 1 + + // and: Secondary cache request 'expiry' parameter matches to the Prebid Cache 'cache.min.expiry' config property + val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + secondaryCacheRequest.puts.size shouldBe 1 + secondaryCacheRequest.puts[0].expiry shouldBe configCacheMinExpiry + } +}) diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt new file mode 100644 index 0000000..f42f470 --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt @@ -0,0 +1,13 @@ +package org.prebid.cache.functional.model.request + +import com.fasterxml.jackson.annotation.JsonValue + +enum class MediaType { + + JSON, XML, UNSUPPORTED; + + @JsonValue + fun getValue(): String { + return name.lowercase() + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt new file mode 100644 index 0000000..2688fef --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt @@ -0,0 +1,32 @@ +package org.prebid.cache.functional.model.request + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL +import org.prebid.cache.functional.model.request.MediaType.JSON +import org.prebid.cache.functional.model.request.MediaType.XML +import org.prebid.cache.functional.util.PrebidCacheUtil + +@JsonInclude(NON_NULL) +data class PayloadTransfer( + var type: MediaType, + var key: String? = null, + var value: String, + var expiry: Long? = null, + var ttlseconds: Long? = null, + var prefix: String? = null +) { + + companion object { + fun getDefaultJsonPayloadTransfer(): PayloadTransfer { + return PayloadTransfer(type = JSON, + value = PrebidCacheUtil.objectMapper.writeValueAsString(TransferValue.getDefaultJsonValue()), + expiry = 300) + } + + fun getDefaultXmlPayloadTransfer(): PayloadTransfer { + return PayloadTransfer(type = XML, + value = PrebidCacheUtil.objectMapper.writeValueAsString(TransferValue.getDefaultXmlValue()), + expiry = 300) + } + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt new file mode 100644 index 0000000..d1c093f --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt @@ -0,0 +1,14 @@ +package org.prebid.cache.functional.model.request + +data class RequestObject(var puts: List) { + + companion object { + fun getDefaultJsonRequestObject(): RequestObject { + return RequestObject(puts = listOf(PayloadTransfer.getDefaultJsonPayloadTransfer())) + } + + fun getDefaultXmlRequestObject(): RequestObject { + return RequestObject(puts = listOf(PayloadTransfer.getDefaultXmlPayloadTransfer())) + } + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/TransferValue.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/TransferValue.kt new file mode 100644 index 0000000..c02305b --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/TransferValue.kt @@ -0,0 +1,16 @@ +package org.prebid.cache.functional.model.request + +data class TransferValue(var adm: String, var width: Int? = null, var height: Int? = null) { + + companion object { + fun getDefaultJsonValue(): TransferValue { + return TransferValue(adm = "\\n
\\\"\\\"
\\n\\n\\n\\\"\\\"
\\n\",\n", + width = 300, + height = 250) + } + + fun getDefaultXmlValue(): TransferValue { + return TransferValue(adm = "\\r\\n \\r\\n <\\/html>\\r\\n ]]>\\r\\n <\\/creativeCode>\\r\\n<\\/xml>\"") + } + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/model/response/ResponseObject.kt b/src/test/kotlin/org/prebid/cache/functional/model/response/ResponseObject.kt new file mode 100644 index 0000000..5d479a1 --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/model/response/ResponseObject.kt @@ -0,0 +1,3 @@ +package org.prebid.cache.functional.model.response + +data class ResponseObject(val responses: List) diff --git a/src/test/kotlin/org/prebid/cache/functional/model/response/ResponseUuid.kt b/src/test/kotlin/org/prebid/cache/functional/model/response/ResponseUuid.kt new file mode 100644 index 0000000..6b73eff --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/model/response/ResponseUuid.kt @@ -0,0 +1,3 @@ +package org.prebid.cache.functional.model.response + +data class ResponseUuid(val uuid: String) diff --git a/src/test/kotlin/org/prebid/cache/functional/service/ApiException.kt b/src/test/kotlin/org/prebid/cache/functional/service/ApiException.kt new file mode 100644 index 0000000..17c1a62 --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/service/ApiException.kt @@ -0,0 +1,3 @@ +package org.prebid.cache.functional.service + +data class ApiException(val statusCode: Int, val responseBody: String) : Exception() diff --git a/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt new file mode 100644 index 0000000..513b8e6 --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt @@ -0,0 +1,53 @@ +package org.prebid.cache.functional.service + +import io.restassured.RestAssured.given +import io.restassured.builder.RequestSpecBuilder +import io.restassured.http.ContentType.JSON +import io.restassured.response.Response +import io.restassured.specification.RequestSpecification +import org.prebid.cache.functional.model.request.RequestObject +import org.prebid.cache.functional.model.response.ResponseObject + +class PrebidCacheApi(prebidCacheUrl: String) { + + companion object { + private const val CACHE_ENDPOINT = "/cache" + private const val UUID_QUERY_PARAMETER = "uuid" + private const val SECONDARY_CACHE_QUERY_PARAMETER = "secondaryCache" + } + + private val requestSpecification: RequestSpecification + + init { + requestSpecification = buildRequestSpecification(prebidCacheUrl) + } + + fun getCache(uuid: String?): Response { + val request = given(requestSpecification) + if (uuid != null) request.queryParam(UUID_QUERY_PARAMETER, uuid) + val response: Response = request.get(CACHE_ENDPOINT) + + checkResponseStatusCode(response) + return response + } + + fun postCache(requestObject: RequestObject, secondaryCache: String? = null): ResponseObject { + val request = given(requestSpecification).body(requestObject) + if (secondaryCache != null) request.queryParam(SECONDARY_CACHE_QUERY_PARAMETER, secondaryCache) + val response: Response = request.post(CACHE_ENDPOINT) + + checkResponseStatusCode(response) + return response.`as`(ResponseObject::class.java) + } + + private fun checkResponseStatusCode(response: Response) { + val statusCode = response.statusCode + if (statusCode != 200) throw ApiException(statusCode, response.body.asString()) + } + + private fun buildRequestSpecification(prebidCacheUrl: String): RequestSpecification { + return RequestSpecBuilder().setBaseUri(prebidCacheUrl) + .setContentType(JSON) + .build() + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt new file mode 100644 index 0000000..048dafd --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt @@ -0,0 +1,33 @@ +package org.prebid.cache.functional.testcontainers + +import org.prebid.cache.functional.testcontainers.container.AerospikeContainer +import org.prebid.cache.functional.testcontainers.container.RedisContainer +import org.prebid.cache.functional.testcontainers.container.WebCacheContainer +import org.testcontainers.containers.Network +import org.testcontainers.lifecycle.Startables + +abstract class ContainerDependencies { + + companion object { + private const val redisImageName = "redis:6.2.6-alpine" + private const val aerospikeImageName = "aerospike:ce-5.7.0.11" + private const val prebidCacheImageName = "prebid-cache:latest" + private const val mockServerImageVersion = "5.12.0" + + val network: Network = Network.newNetwork() + val redisContainer: RedisContainer = RedisContainer(redisImageName).withNetwork(network) + val aerospikeContainer: AerospikeContainer = AerospikeContainer(aerospikeImageName).withNetwork(network) + val webCacheContainer: WebCacheContainer = WebCacheContainer(mockServerImageVersion).withNetwork(network) as WebCacheContainer + val prebidCacheContainerPool = PrebidCacheContainerPool(prebidCacheImageName) + + fun startCacheServerContainers() { + Startables.deepStart(listOf(redisContainer, aerospikeContainer)) + .join() + } + + fun stopCacheServerContainers() { + listOf(redisContainer, aerospikeContainer).parallelStream() + .forEach { it.stop() } + } + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerStartable.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerStartable.kt new file mode 100644 index 0000000..277433d --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerStartable.kt @@ -0,0 +1,16 @@ +package org.prebid.cache.functional.testcontainers + +import io.kotest.core.annotation.AutoScan +import io.kotest.core.listeners.ProjectListener + +@AutoScan +object ContainerStartable : ProjectListener { + + override suspend fun beforeProject() { + ContainerDependencies.startCacheServerContainers() + } + + override suspend fun afterProject() { + ContainerDependencies.stopCacheServerContainers() + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt new file mode 100644 index 0000000..8dc9038 --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt @@ -0,0 +1,73 @@ +package org.prebid.cache.functional.testcontainers + +import org.prebid.cache.functional.testcontainers.container.AerospikeContainer +import org.prebid.cache.functional.testcontainers.container.AerospikeContainer.Companion.NAMESPACE +import org.prebid.cache.functional.testcontainers.container.RedisContainer +import org.prebid.cache.functional.testcontainers.container.WebCacheContainer.Companion.WEB_CACHE_PATH + +class PrebidCacheContainerConfig(private val redisHost: String, private val aerospikeHost: String) { + + fun getBaseRedisConfig(allowExternalUuid: String): Map { + return getBaseConfig(allowExternalUuid) + getRedisConfig() + } + + fun getBaseAerospikeConfig(allowExternalUuid: String): Map { + return getBaseConfig(allowExternalUuid) + getAerospikeConfig() + } + + fun getRedisConfig(): Map { + return mapOf( + "spring.redis.port" to RedisContainer.PORT.toString(), + "spring.redis.host" to redisHost, + "spring.redis.timeout" to "300" + ) + } + + fun getAerospikeConfig(aerospikeNamespace: String = NAMESPACE): Map { + return mapOf( + "spring.aerospike.port" to AerospikeContainer.PORT.toString(), + "spring.aerospike.host" to aerospikeHost, + "spring.aerospike.cores" to "4", + "spring.aerospike.password" to "", + "spring.aerospike.first_backoff" to "300", + "spring.aerospike.max_backoff" to "1000", + "spring.aerospike.max_retry" to "3", + "spring.aerospike.namespace" to aerospikeNamespace + ) + } + + fun getCacheExpiryConfig(minExpiry: String = "15", maxExpiry: String = "28800"): Map { + return mapOf( + "cache.min.expiry" to minExpiry, + "cache.max.expiry" to maxExpiry, + "cache.expiry.sec" to "300" + ) + } + + fun getCacheTimeoutConfig(timeoutMs: String): Map { + return mapOf("cache.timeout.ms" to timeoutMs) + } + + fun getAerospikePreventUuidDuplicationConfig(preventUuidDuplication: String): Map { + return mapOf("spring.aerospike.prevent-u-u-i-d-duplication" to preventUuidDuplication) + } + + fun getSecondaryCacheConfig(secondaryCacheUri: String): Map { + return mapOf( + "cache.secondary_cache_path" to WEB_CACHE_PATH, + "cache.secondary-uris" to secondaryCacheUri + ) + } + + private fun getBaseConfig(allowExternalUuid: String): Map { + return getCachePrefixConfig() + getCacheExpiryConfig() + getAllowExternalUuidConfig(allowExternalUuid) + } + + private fun getCachePrefixConfig(): Map { + return mapOf("cache.prefix" to "prebid_") + } + + private fun getAllowExternalUuidConfig(allowExternalUuid: String): Map { + return mapOf("cache.allow-external-u-u-i-d" to allowExternalUuid) + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt new file mode 100644 index 0000000..514978c --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt @@ -0,0 +1,33 @@ +package org.prebid.cache.functional.testcontainers + +import org.prebid.cache.functional.testcontainers.container.PrebidCacheContainer + +class PrebidCacheContainerPool(private val containerImageName: String) { + + companion object { + private val MAX_CONTAINER_COUNT: Int = (System.getProperty("max.containers.count") ?: "3").toInt() + private val prebidCacheContainerMap: MutableMap, PrebidCacheContainer> = mutableMapOf() + } + + fun getPrebidCacheContainer(config: Map): PrebidCacheContainer { + if (prebidCacheContainerMap.size >= MAX_CONTAINER_COUNT) { + val oldestContainerConfig = + prebidCacheContainerMap.entries.sortedBy { it.value.containerInfo.created }[0].key + stopPrebidCacheContainer(oldestContainerConfig) + } + + return if (prebidCacheContainerMap.containsKey(config)) { + prebidCacheContainerMap.getValue(config) + } else { + val prebidCacheContainer = PrebidCacheContainer(containerImageName, config) + prebidCacheContainer.start() + prebidCacheContainerMap[config] = prebidCacheContainer + prebidCacheContainer + } + } + + fun stopPrebidCacheContainer(config: Map) { + if (prebidCacheContainerMap.containsKey(config)) prebidCacheContainerMap.getValue(config).stop() + prebidCacheContainerMap.remove(config) + } +} 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 new file mode 100644 index 0000000..0978564 --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/client/WebCacheContainerClient.kt @@ -0,0 +1,38 @@ +package org.prebid.cache.functional.testcontainers.client + +import org.mockserver.client.MockServerClient +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.prebid.cache.functional.testcontainers.container.WebCacheContainer.Companion.WEB_CACHE_PATH + +class WebCacheContainerClient(mockServerHost: String, mockServerPort: Int) { + + private val mockServerClient: MockServerClient + + init { + mockServerClient = MockServerClient(mockServerHost, mockServerPort) + initResponse() + } + + fun getRecordedRequests(uuidKey: String): Array? { + return mockServerClient.retrieveRecordedRequests(getSecondaryCacheRequest(uuidKey)) + } + + private fun getSecondaryCacheRequest(): HttpRequest { + return request().withMethod("POST") + .withPath("/$WEB_CACHE_PATH") + } + + private fun getSecondaryCacheRequest(uuidKey: String): HttpRequest { + return request().withMethod("POST") + .withPath("/$WEB_CACHE_PATH") + .withBody(jsonPath("\$.puts[?(@.key == '$uuidKey')]")) + } + + private fun initResponse() { + mockServerClient.`when`(getSecondaryCacheRequest()) + .respond(response().withStatusCode(200)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/AerospikeContainer.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/AerospikeContainer.kt new file mode 100644 index 0000000..0de7e2b --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/AerospikeContainer.kt @@ -0,0 +1,23 @@ +package org.prebid.cache.functional.testcontainers.container + +import org.testcontainers.containers.GenericContainer + +class AerospikeContainer(imageName: String) : GenericContainer(imageName) { + + companion object { + const val PORT = 3000 + const val NAMESPACE = "prebid_cache" + } + + init { + withNamespace() + } + + fun getContainerHost(): String { + return networkAliases.first() + } + + private fun withNamespace(): AerospikeContainer { + return withEnv(mapOf("NAMESPACE" to NAMESPACE)) + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt new file mode 100644 index 0000000..fed510b --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt @@ -0,0 +1,58 @@ +package org.prebid.cache.functional.testcontainers.container + +import org.prebid.cache.functional.testcontainers.ContainerDependencies +import org.testcontainers.containers.GenericContainer + +class PrebidCacheContainer(imageName: String, config: Map) : + GenericContainer(imageName) { + + companion object { + private const val PORT = 8080 + private const val DEBUG_PORT = 8000 + private const val FIXED_EXPOSED_APPLICATION_PORT = 49100 + private const val FIXED_EXPOSED_DEBUG_PORT = 49101 + + private val USE_FIXED_PORTS = (System.getProperty("useFixedContainerPorts") ?: "false").toBoolean() + } + + init { + withNetwork(ContainerDependencies.network) + withExposedPorts(PORT, DEBUG_PORT) + withFixedExposedPorts() + withDebug() + withConfig(config) + } + + fun getHostUri(): String { + return "http://$host:${getHostPort()}" + } + + private fun getHostPort(): Int { + return getMappedPort(PORT) + } + + private fun withConfig(config: Map): PrebidCacheContainer { + return withEnv(normalizeProperties(config)) + } + + private fun withFixedExposedPorts() { + if (USE_FIXED_PORTS) { + addFixedExposedPort(FIXED_EXPOSED_APPLICATION_PORT, PORT) + addFixedExposedPort(FIXED_EXPOSED_DEBUG_PORT, DEBUG_PORT) + } + } + + private fun withDebug(): PrebidCacheContainer { + return withEnv("JAVA_TOOL_OPTIONS", + "-agentlib:jdwp=transport=dt_socket,address=*:$DEBUG_PORT,server=y,suspend=n") + } + + private fun normalizeProperties(config: Map): Map { + return config.mapKeys { normalizeProperty(it.key) } + } + + private fun normalizeProperty(property: String): String { + return property.replace(".", "_") + .replace("-", "") + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/RedisContainer.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/RedisContainer.kt new file mode 100644 index 0000000..65a8b5d --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/RedisContainer.kt @@ -0,0 +1,14 @@ +package org.prebid.cache.functional.testcontainers.container + +import org.testcontainers.containers.GenericContainer + +class RedisContainer(imageName: String) : GenericContainer(imageName) { + + companion object { + const val PORT = 6379 + } + + fun getContainerHost(): String { + return networkAliases.first() + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/WebCacheContainer.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/WebCacheContainer.kt new file mode 100644 index 0000000..46dfead --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/WebCacheContainer.kt @@ -0,0 +1,15 @@ +package org.prebid.cache.functional.testcontainers.container + +import org.testcontainers.containers.MockServerContainer + +class WebCacheContainer(mockServerImageVersion: String) : MockServerContainer(mockServerImageVersion) { + + companion object { + const val PORT = MockServerContainer.PORT + const val WEB_CACHE_PATH = "cache" + } + + fun getContainerHost(): String { + return networkAliases.first() + } +} diff --git a/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt b/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt new file mode 100644 index 0000000..fde54cf --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt @@ -0,0 +1,22 @@ +package org.prebid.cache.functional.util + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import java.util.* + +class PrebidCacheUtil { + + companion object { + val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()) + + fun getRandomUuid(): String { + return UUID.randomUUID().toString() + } + + fun getRandomString(length: Int = 16): String { + val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') + return (1..length).map { allowedChars.random() } + .joinToString("") + } + } +} From a9b17ed693696e8ad1b5ec464a1a109400d66888 Mon Sep 17 00:00:00 2001 From: mhupalo Date: Fri, 25 Mar 2022 18:25:54 +0200 Subject: [PATCH 02/11] Start using more Kotlin style; set constants where possible; replace rest assured with Ktor --- pom.xml | 20 +-- .../cache/functional/AerospikeCacheSpec.kt | 69 ++++++---- .../org/prebid/cache/functional/BaseSpec.kt | 10 +- .../cache/functional/GeneralCacheSpec.kt | 120 ++++++++++++------ .../prebid/cache/functional/RedisCacheSpec.kt | 27 ++-- .../cache/functional/SecondaryCacheSpec.kt | 40 +++--- .../prebid/cache/functional/mapper/Mapper.kt | 6 + .../functional/model/request/MediaType.kt | 4 +- .../model/request/PayloadTransfer.kt | 36 +++--- .../functional/model/request/RequestObject.kt | 10 +- .../functional/model/request/TransferValue.kt | 14 +- .../functional/service/PrebidCacheApi.kt | 94 ++++++++------ .../testcontainers/ContainerDependencies.kt | 10 +- .../testcontainers/ContainerStartable.kt | 8 +- .../PrebidCacheContainerConfig.kt | 87 ++++++------- .../PrebidCacheContainerPool.kt | 10 +- .../client/WebCacheContainerClient.kt | 31 +++-- .../container/AerospikeContainer.kt | 16 +-- .../container/PrebidCacheContainer.kt | 51 +++----- .../container/RedisContainer.kt | 6 +- .../container/WebCacheContainer.kt | 6 +- .../cache/functional/util/PrebidCacheUtil.kt | 23 +--- 22 files changed, 372 insertions(+), 326 deletions(-) create mode 100644 src/test/kotlin/org/prebid/cache/functional/mapper/Mapper.kt diff --git a/pom.xml b/pom.xml index 2353059..a3603cf 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 1.6.10 1.16.3 5.12.0 - 3.3.0 + 1.6.8 2.9.10 0.39.1 @@ -260,21 +260,15 @@ test - io.rest-assured - rest-assured - ${restassured.version} + io.ktor + ktor-client-apache + ${ktor.version} test - io.rest-assured - json-path - ${restassured.version} - test - - - io.rest-assured - xml-path - ${restassured.version} + io.ktor + ktor-client-jackson + ${ktor.version} test diff --git a/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt index 6f6ebf7..091606b 100644 --- a/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt @@ -1,18 +1,25 @@ 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.matchers.collections.shouldContain import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain +import io.ktor.client.statement.readText import org.prebid.cache.functional.BaseSpec.Companion.prebidCacheConfig +import org.prebid.cache.functional.mapper.objectMapper import org.prebid.cache.functional.model.request.PayloadTransfer import org.prebid.cache.functional.model.request.RequestObject import org.prebid.cache.functional.model.request.TransferValue import org.prebid.cache.functional.model.response.ResponseObject import org.prebid.cache.functional.service.ApiException import org.prebid.cache.functional.testcontainers.ContainerDependencies -import org.prebid.cache.functional.util.PrebidCacheUtil +import org.prebid.cache.functional.util.getRandomString +import org.prebid.cache.functional.util.getRandomUuid +import org.springframework.http.HttpStatus.BAD_REQUEST +import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR +import org.springframework.http.HttpStatus.NOT_FOUND class AerospikeCacheSpec : ShouldSpec({ @@ -22,17 +29,19 @@ class AerospikeCacheSpec : ShouldSpec({ val cachePrefix = config["cache.prefix"] // when: GET cache endpoint with random UUID is called - val randomUuid = PrebidCacheUtil.getRandomUuid() + val randomUuid = getRandomUuid() val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi(config).getCache(randomUuid) } // then: Not Found exception is thrown - exception.statusCode shouldBe 404 - exception.responseBody shouldContain "\"message\":\"Resource Not Found: uuid $cachePrefix$randomUuid\"" + assertSoftly { + exception.statusCode shouldBe NOT_FOUND.value() + exception.responseBody shouldContain "\"message\":\"Resource Not Found: uuid $cachePrefix$randomUuid\"" + } } should("rethrow an exception from Aerospike cache server when such happens") { // given: Prebid Cache with not matched to Aerospike server namespace - val unmatchedNamespace = PrebidCacheUtil.getRandomString() + val unmatchedNamespace = getRandomString() val config = prebidCacheConfig.getCacheExpiryConfig() + prebidCacheConfig.getAerospikeConfig(unmatchedNamespace) val prebidCacheApi = BaseSpec.getPrebidCacheApi(config) @@ -43,8 +52,10 @@ class AerospikeCacheSpec : ShouldSpec({ val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } // then: Internal Server Error exception is thrown - exception.statusCode shouldBe 500 - exception.responseBody shouldContain "Namespace not found in partition map: $unmatchedNamespace" + assertSoftly { + exception.statusCode shouldBe INTERNAL_SERVER_ERROR.value() + exception.responseBody shouldContain "Namespace not found in partition map: $unmatchedNamespace" + } // cleanup ContainerDependencies.prebidCacheContainerPool.stopPrebidCacheContainer(config) @@ -52,11 +63,13 @@ class AerospikeCacheSpec : ShouldSpec({ should("throw an exception when aerospike.prevent_UUID_duplication=true and request with already existing UUID is send") { // given: Prebid Cache with aerospike.prevent_UUID_duplication=true property - val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseAerospikeConfig("true") + - prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("true")) + val prebidCacheApi = BaseSpec.getPrebidCacheApi( + prebidCacheConfig.getBaseAerospikeConfig("true") + + prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("true") + ) // and: First request object with set UUID - val uuid = PrebidCacheUtil.getRandomUuid() + val uuid = getRandomUuid() val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() xmlPayloadTransfer.key = uuid val requestObject = RequestObject(listOf(xmlPayloadTransfer)) @@ -73,20 +86,24 @@ class AerospikeCacheSpec : ShouldSpec({ val exception = shouldThrowExactly { prebidCacheApi.postCache(secondRequestObject) } // then: Bad Request exception is thrown - exception.statusCode shouldBe 400 - exception.responseBody shouldContain "\"message\":\"UUID duplication.\"" + assertSoftly { + exception.statusCode shouldBe BAD_REQUEST.value() + exception.responseBody shouldContain "\"message\":\"UUID duplication.\"" + } } should("return back two request UUIDs when allow_external_UUID=true and 2 payload transfers were successfully cached in Aerospike") { // given: Prebid Cache with allow_external_UUID=true property - val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseAerospikeConfig("true") + - prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false")) + val prebidCacheApi = BaseSpec.getPrebidCacheApi( + prebidCacheConfig.getBaseAerospikeConfig("true") + + prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false") + ) // and: Request object with 2 payload transfers and set UUIDs is prepared val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() - xmlPayloadTransfer.key = PrebidCacheUtil.getRandomUuid() - jsonPayloadTransfer.key = PrebidCacheUtil.getRandomUuid() + xmlPayloadTransfer.key = getRandomUuid() + jsonPayloadTransfer.key = getRandomUuid() val requestObject = RequestObject(listOf(xmlPayloadTransfer, jsonPayloadTransfer)) // when: POST cache endpoint is called @@ -103,11 +120,13 @@ class AerospikeCacheSpec : ShouldSpec({ should("update existing cache record when aerospike.prevent_UUID_duplication=false and request with already existing UUID is send") { // given: Prebid Cache with aerospike.prevent_UUID_duplication=false - val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseAerospikeConfig("true") + - prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false")) + val prebidCacheApi = BaseSpec.getPrebidCacheApi( + prebidCacheConfig.getBaseAerospikeConfig("true") + + prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false") + ) // and: First request object - val uuid = PrebidCacheUtil.getRandomUuid() + val uuid = getRandomUuid() val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() xmlPayloadTransfer.key = uuid val requestObject = RequestObject(listOf(xmlPayloadTransfer)) @@ -119,7 +138,7 @@ class AerospikeCacheSpec : ShouldSpec({ val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() jsonPayloadTransfer.key = uuid val secondRequestObject = RequestObject(listOf(jsonPayloadTransfer)) - val requestTransferValue = PrebidCacheUtil.objectMapper.readValue(secondRequestObject.puts[0].value, TransferValue::class.java) + val requestTransferValue = objectMapper.readValue(secondRequestObject.puts[0].value, TransferValue::class.java) // when: POST cache endpoint is called for the second time val responseObject = prebidCacheApi.postCache(secondRequestObject) @@ -131,10 +150,12 @@ class AerospikeCacheSpec : ShouldSpec({ // and: Cache record was updated in Aerospike with a second request object payload val getCacheResponse = prebidCacheApi.getCache(responseObject.responses[0].uuid) - val responseTransferValue = getCacheResponse.`as`(TransferValue::class.java) + val responseTransferValue = objectMapper.readValue(getCacheResponse.readText(), TransferValue::class.java) - responseTransferValue.adm shouldBe requestTransferValue.adm - responseTransferValue.width shouldBe requestTransferValue.width - responseTransferValue.height shouldBe requestTransferValue.height + assertSoftly { + responseTransferValue.adm shouldBe requestTransferValue.adm + responseTransferValue.width shouldBe requestTransferValue.width + responseTransferValue.height shouldBe requestTransferValue.height + } } }) diff --git a/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt b/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt index 71d7aab..fcafd88 100644 --- a/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt @@ -7,13 +7,15 @@ import org.prebid.cache.functional.testcontainers.PrebidCacheContainerConfig abstract class BaseSpec { companion object { - val prebidCacheConfig = PrebidCacheContainerConfig(ContainerDependencies.redisContainer.getContainerHost(), - ContainerDependencies.aerospikeContainer.getContainerHost()) + val prebidCacheConfig = PrebidCacheContainerConfig( + ContainerDependencies.redisContainer.getContainerHost(), + ContainerDependencies.aerospikeContainer.getContainerHost() + ) fun getPrebidCacheApi(config: Map = prebidCacheConfig.getBaseRedisConfig("false")): PrebidCacheApi { val prebidCacheContainer = - ContainerDependencies.prebidCacheContainerPool.getPrebidCacheContainer(config) - return PrebidCacheApi(prebidCacheContainer.getHostUri()) + ContainerDependencies.prebidCacheContainerPool.getPrebidCacheContainer(config) + return PrebidCacheApi(prebidCacheContainer.host, prebidCacheContainer.getHostPort()) } } } diff --git a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt index 77ecea4..0d659ce 100644 --- a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt @@ -1,14 +1,22 @@ 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.matchers.shouldBe import io.kotest.matchers.string.shouldContain +import io.ktor.client.statement.readText +import io.ktor.http.contentType +import org.prebid.cache.functional.mapper.objectMapper import org.prebid.cache.functional.model.request.MediaType.UNSUPPORTED import org.prebid.cache.functional.model.request.RequestObject import org.prebid.cache.functional.model.request.TransferValue import org.prebid.cache.functional.service.ApiException -import org.prebid.cache.functional.util.PrebidCacheUtil +import org.prebid.cache.functional.util.getRandomString +import org.prebid.cache.functional.util.getRandomUuid +import org.springframework.http.HttpStatus.BAD_REQUEST +import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR +import org.springframework.http.HttpStatus.UNSUPPORTED_MEDIA_TYPE class GeneralCacheSpec : ShouldSpec({ @@ -17,21 +25,25 @@ class GeneralCacheSpec : ShouldSpec({ val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi().getCache(null) } // then: Bad Request exception is thrown - exception.statusCode shouldBe 400 - exception.responseBody shouldContain "\"message\":\"Invalid Parameter(s): uuid not found.\"" + assertSoftly { + exception.statusCode shouldBe BAD_REQUEST.value() + exception.responseBody shouldContain "\"message\":\"Invalid Parameter(s): uuid not found.\"" + } } should("throw an exception when payload transfer key is given and allow_external_UUID=false") { // given: Request object with set payload transfer key val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].key = getRandomUuid() // when: POST cache endpoint is called val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi().postCache(requestObject) } // then: Bad Request exception is thrown - exception.statusCode shouldBe 400 - exception.responseBody shouldContain "\"message\":\"Prebid cache host forbids specifying UUID in request.\"" + assertSoftly { + exception.statusCode shouldBe BAD_REQUEST.value() + exception.responseBody shouldContain "\"message\":\"Prebid cache host forbids specifying UUID in request.\"" + } } should("throw an exception when allow_external_UUID=true and payload transfer key not in UUID format is given") { @@ -40,14 +52,16 @@ class GeneralCacheSpec : ShouldSpec({ // and: Request object with set payload transfer key not in UUID format val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + "*" + requestObject.puts[0].key = getRandomUuid() + "*" // when: POST cache endpoint is called val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } // then: Bad Request exception is thrown - exception.statusCode shouldBe 400 - exception.responseBody shouldContain "\"message\":\"Invalid UUID: [${requestObject.puts[0].key}].\"" + assertSoftly { + exception.statusCode shouldBe BAD_REQUEST.value() + exception.responseBody shouldContain "\"message\":\"Invalid UUID: [${requestObject.puts[0].key}].\"" + } } should("throw an exception when allow_external_UUID=true and empty payload transfer key is given") { @@ -62,8 +76,10 @@ class GeneralCacheSpec : ShouldSpec({ val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } // then: Bad Request exception is thrown - exception.statusCode shouldBe 400 - exception.responseBody shouldContain "\"message\":\"Invalid UUID: [].\"" + assertSoftly { + exception.statusCode shouldBe BAD_REQUEST.value() + exception.responseBody shouldContain "\"message\":\"Invalid UUID: [].\"" + } } should("throw an exception when payload object with unsupported media type is fetched from repository") { @@ -75,18 +91,23 @@ class GeneralCacheSpec : ShouldSpec({ val response = BaseSpec.getPrebidCacheApi().postCache(requestObject) // when: GET cache endpoint is called - val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi().getCache(response.responses[0].uuid) } + val exception = + shouldThrowExactly { BaseSpec.getPrebidCacheApi().getCache(response.responses[0].uuid) } // then: Unsupported Media Type exception is thrown - exception.statusCode shouldBe 415 - exception.responseBody shouldContain "\"message\":\"Unsupported Media Type.\"" + assertSoftly { + exception.statusCode shouldBe UNSUPPORTED_MEDIA_TYPE.value() + exception.responseBody shouldContain "\"message\":\"Unsupported Media Type.\"" + } } should("throw an exception on POST when request processing takes > than 'cache.timeout.ms' config property") { // given: Prebid Cache with a low cache request processing timeout ms val requestTimeoutMs = "1" - val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("false") + - BaseSpec.prebidCacheConfig.getCacheTimeoutConfig(requestTimeoutMs)) + val prebidCacheApi = BaseSpec.getPrebidCacheApi( + BaseSpec.prebidCacheConfig.getBaseRedisConfig("false") + + BaseSpec.prebidCacheConfig.getCacheTimeoutConfig(requestTimeoutMs) + ) // and: Request object val requestObject = RequestObject.getDefaultJsonRequestObject() @@ -95,30 +116,36 @@ class GeneralCacheSpec : ShouldSpec({ val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } // then: Internal Server Exception is thrown - exception.statusCode shouldBe 500 - exception.responseBody shouldContain "\"message\":\"Did not observe any item or terminal signal within " + - "${requestTimeoutMs}ms in 'circuitBreaker' (and no fallback has been configured)\"" + assertSoftly { + exception.statusCode shouldBe INTERNAL_SERVER_ERROR.value() + exception.responseBody shouldContain "\"message\":\"Did not observe any item or terminal signal within " + + "${requestTimeoutMs}ms in 'circuitBreaker' (and no fallback has been configured)\"" + } } should("throw an exception on GET when request processing takes > than 'cache.timeout.ms' config property") { // given: Prebid Cache with a low cache request processing timeout ms val requestTimeoutMs = "1" - val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("false") + - BaseSpec.prebidCacheConfig.getCacheTimeoutConfig(requestTimeoutMs)) + val prebidCacheApi = BaseSpec.getPrebidCacheApi( + BaseSpec.prebidCacheConfig.getBaseRedisConfig("false") + + BaseSpec.prebidCacheConfig.getCacheTimeoutConfig(requestTimeoutMs) + ) // and: GET cache endpoint is called - val exception = shouldThrowExactly { prebidCacheApi.getCache(PrebidCacheUtil.getRandomUuid()) } + val exception = shouldThrowExactly { prebidCacheApi.getCache(getRandomUuid()) } // then: Internal Server Exception is thrown - exception.statusCode shouldBe 500 - exception.responseBody shouldContain "\"message\":\"Did not observe any item or terminal signal within " + - "${requestTimeoutMs}ms in 'circuitBreaker' (and no fallback has been configured)\"" + assertSoftly { + exception.statusCode shouldBe INTERNAL_SERVER_ERROR.value() + exception.responseBody shouldContain "\"message\":\"Did not observe any item or terminal signal within " + + "${requestTimeoutMs}ms in 'circuitBreaker' (and no fallback has been configured)\"" + } } should("return the same JSON transfer value which was saved to cache") { // given: Request object with JSON transfer value val requestObject = RequestObject.getDefaultJsonRequestObject() - val requestTransferValue = PrebidCacheUtil.objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java) + val requestTransferValue = objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java) // and: POST cache endpoint is called val postResponse = BaseSpec.getPrebidCacheApi().postCache(requestObject) @@ -126,19 +153,24 @@ class GeneralCacheSpec : ShouldSpec({ // when: GET cache endpoint is called val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid) - // then: transfer value is returned - getCacheResponse.contentType shouldBe "application/${requestObject.puts[0].type.getValue()};charset=UTF-8" + // then: response content type is the same as request object type + getCacheResponse.contentType()?.contentType shouldBe "application" + getCacheResponse.contentType()?.contentSubtype shouldBe requestObject.puts[0].type.getValue() - val responseTransferValue = getCacheResponse.`as`(TransferValue::class.java) - responseTransferValue.adm shouldBe requestTransferValue.adm - responseTransferValue.width shouldBe requestTransferValue.width - responseTransferValue.height shouldBe requestTransferValue.height + // and: transfer value is returned + val responseTransferValue = objectMapper.readValue(getCacheResponse.readText(), TransferValue::class.java) + + assertSoftly { + responseTransferValue.adm shouldBe requestTransferValue.adm + responseTransferValue.width shouldBe requestTransferValue.width + responseTransferValue.height shouldBe requestTransferValue.height + } } should("return the same XML transfer value which was saved to cache") { // given: Request object with XML transfer value val requestObject = RequestObject.getDefaultXmlRequestObject() - val requestTransferValue = PrebidCacheUtil.objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java) + val requestTransferValue = objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java) // and: POST cache endpoint is called val postResponse = BaseSpec.getPrebidCacheApi().postCache(requestObject) @@ -146,20 +178,24 @@ class GeneralCacheSpec : ShouldSpec({ // when: GET cache endpoint is called val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid) - // then: transfer value is returned - getCacheResponse.contentType shouldBe "application/${requestObject.puts[0].type.getValue()}" + // then: response content type is the same as request object type + getCacheResponse.contentType()?.contentType shouldBe "application" + getCacheResponse.contentType()?.contentSubtype shouldBe requestObject.puts[0].type.getValue() + + // and: transfer value is returned + val responseTransferValue = objectMapper.readValue(getCacheResponse.readText(), TransferValue::class.java) - val responseTransferValue = - PrebidCacheUtil.objectMapper.readValue(getCacheResponse.body.asString(), TransferValue::class.java) - responseTransferValue.adm shouldBe requestTransferValue.adm - responseTransferValue.width shouldBe requestTransferValue.width - responseTransferValue.height shouldBe requestTransferValue.height + assertSoftly { + responseTransferValue.adm shouldBe requestTransferValue.adm + responseTransferValue.width shouldBe requestTransferValue.width + responseTransferValue.height shouldBe requestTransferValue.height + } } should("return the same String transfer value which was saved to cache") { // given: Request object with set transfer value as plain String val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].value = PrebidCacheUtil.getRandomString() + requestObject.puts[0].value = getRandomString() // and: POST cache endpoint is called val postResponse = BaseSpec.getPrebidCacheApi().postCache(requestObject) @@ -168,6 +204,6 @@ class GeneralCacheSpec : ShouldSpec({ val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid) // then: transfer value as a plain String is returned - getCacheResponse.body.asString() shouldBe requestObject.puts[0].value + getCacheResponse.readText() shouldBe requestObject.puts[0].value } }) diff --git a/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt index f3b0a6c..b68a194 100644 --- a/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt @@ -1,5 +1,6 @@ package org.prebid.cache.functional +import io.kotest.assertions.assertSoftly import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.core.spec.style.ShouldSpec @@ -12,8 +13,10 @@ import org.prebid.cache.functional.model.request.RequestObject import org.prebid.cache.functional.model.response.ResponseObject import org.prebid.cache.functional.service.ApiException import org.prebid.cache.functional.testcontainers.ContainerDependencies -import org.prebid.cache.functional.util.PrebidCacheUtil -import java.util.* +import org.prebid.cache.functional.util.getRandomUuid +import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR +import org.springframework.http.HttpStatus.NOT_FOUND +import java.util.UUID class RedisCacheSpec : ShouldSpec({ @@ -23,12 +26,14 @@ class RedisCacheSpec : ShouldSpec({ val cachePrefix = config["cache.prefix"] // when: GET cache endpoint with random UUID is called - val randomUuid = PrebidCacheUtil.getRandomUuid() + val randomUuid = getRandomUuid() val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi(config).getCache(randomUuid) } // then: Not Found exception is thrown - exception.statusCode shouldBe 404 - exception.responseBody shouldContain "\"message\":\"Resource Not Found: uuid $cachePrefix$randomUuid\"" + assertSoftly { + exception.statusCode shouldBe NOT_FOUND.value() + exception.responseBody shouldContain "\"message\":\"Resource Not Found: uuid $cachePrefix$randomUuid\"" + } } should("rethrow an exception from Redis cache server when such happens") { @@ -43,8 +48,10 @@ class RedisCacheSpec : ShouldSpec({ val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } // then: Internal Server Error exception is thrown - exception.statusCode shouldBe 500 - exception.responseBody shouldContain "\"message\":\"ERR invalid expire time in setex\"" + assertSoftly { + exception.statusCode shouldBe INTERNAL_SERVER_ERROR.value() + exception.responseBody shouldContain "\"message\":\"ERR invalid expire time in setex\"" + } // cleanup ContainerDependencies.prebidCacheContainerPool.stopPrebidCacheContainer(config) @@ -87,7 +94,7 @@ class RedisCacheSpec : ShouldSpec({ // and: Request object with set payload transfer UUID key val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].key = getRandomUuid() // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject) @@ -105,8 +112,8 @@ class RedisCacheSpec : ShouldSpec({ // and: Request object with set 2 payload transfers val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() - xmlPayloadTransfer.key = PrebidCacheUtil.getRandomUuid() - jsonPayloadTransfer.key = PrebidCacheUtil.getRandomUuid() + xmlPayloadTransfer.key = getRandomUuid() + jsonPayloadTransfer.key = getRandomUuid() val requestObject = RequestObject(listOf(xmlPayloadTransfer, jsonPayloadTransfer)) // when: POST cache endpoint is called diff --git a/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt index fab773e..4474dd5 100644 --- a/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt @@ -2,13 +2,14 @@ package org.prebid.cache.functional import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe +import org.prebid.cache.functional.mapper.objectMapper import org.prebid.cache.functional.model.request.RequestObject import org.prebid.cache.functional.model.response.ResponseObject 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.PrebidCacheUtil +import org.prebid.cache.functional.util.getRandomUuid class SecondaryCacheSpec : ShouldSpec({ @@ -22,8 +23,12 @@ class SecondaryCacheSpec : ShouldSpec({ ContainerDependencies.webCacheContainer.start() // and: Mock web cache client is initialized - webCacheContainerClient = WebCacheContainerClient(ContainerDependencies.webCacheContainer.host, ContainerDependencies.webCacheContainer.serverPort) - webCacheContainerUri = "http://${ContainerDependencies.webCacheContainer.getContainerHost()}:${WebCacheContainer.PORT}" + webCacheContainerClient = WebCacheContainerClient( + ContainerDependencies.webCacheContainer.host, + ContainerDependencies.webCacheContainer.serverPort + ) + webCacheContainerUri = + "http://${ContainerDependencies.webCacheContainer.getContainerHost()}:${WebCacheContainer.PORT}" // and: Prebid Cache with allow_external_UUID=true and configured secondary cache is started specPrebidCacheConfig = BaseSpec.prebidCacheConfig.getBaseAerospikeConfig("true") + @@ -39,10 +44,10 @@ class SecondaryCacheSpec : ShouldSpec({ ContainerDependencies.prebidCacheContainerPool.stopPrebidCacheContainer(specPrebidCacheConfig) } - should("send a request to secondary cache when secondary cache is configures and secondaryCache query parameter is given on request") { + should("send a request to secondary cache when secondary cache is configured and secondaryCache query parameter is given on request") { // given: Request object with set payload UUID key val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].key = getRandomUuid() // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") @@ -56,17 +61,18 @@ class SecondaryCacheSpec : ShouldSpec({ secondaryCacheRecordedRequests?.size shouldBe 1 // and: Request contained secondaryCache=yes query parameter - secondaryCacheRecordedRequests?.first()?.queryStringParameters?.containsEntry("secondaryCache", "yes") + secondaryCacheRecordedRequests!!.first().queryStringParameters?.containsEntry("secondaryCache", "yes") // and: Secondary cache request body matched to the Prebid Cache request object - val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + val secondaryCacheRequest = + objectMapper.readValue(secondaryCacheRecordedRequests.first().bodyAsString, RequestObject::class.java) secondaryCacheRequest shouldBe requestObject } should("set cache expiry equals to request 'ttlseconds' when ttlseconds parameter is given") { // given: Request object with set 'ttlseconds' parameter val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].key = getRandomUuid() requestObject.puts[0].ttlseconds = 400 requestObject.puts[0].expiry = 300 @@ -82,7 +88,8 @@ class SecondaryCacheSpec : ShouldSpec({ secondaryCacheRecordedRequests?.size shouldBe 1 // and: Secondary cache request 'expiry' parameter matches to the PBC request 'ttlseconds' parameter - val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + val secondaryCacheRequest = + objectMapper.readValue(secondaryCacheRecordedRequests!!.first().bodyAsString, RequestObject::class.java) secondaryCacheRequest.puts.size shouldBe 1 secondaryCacheRequest.puts[0].expiry shouldBe requestObject.puts[0].ttlseconds } @@ -90,7 +97,7 @@ class SecondaryCacheSpec : ShouldSpec({ should("set cache expiry from 'cache.expiry.sec' configuration property when request 'ttlseconds' and 'expiry' are absent'") { // given: Request object with absent 'ttlseconds' and 'expiry' val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].key = getRandomUuid() requestObject.puts[0].ttlseconds = null requestObject.puts[0].expiry = null @@ -106,7 +113,8 @@ class SecondaryCacheSpec : ShouldSpec({ secondaryCacheRecordedRequests?.size shouldBe 1 // and: Secondary cache request 'expiry' parameter matches to the Prebid Cache 'cache.expiry.sec' config property - val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + val secondaryCacheRequest = + objectMapper.readValue(secondaryCacheRecordedRequests!!.first().bodyAsString, RequestObject::class.java) secondaryCacheRequest.puts.size shouldBe 1 secondaryCacheRequest.puts[0].expiry shouldBe specPrebidCacheConfig["cache.expiry.sec"]?.toLong() } @@ -117,7 +125,7 @@ class SecondaryCacheSpec : ShouldSpec({ // and: Request object with set 'expiry' higher than configuration 'cache.max.expiry' val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].key = getRandomUuid() requestObject.puts[0].ttlseconds = null requestObject.puts[0].expiry = configCacheMaxExpiry!! + 1 @@ -133,7 +141,8 @@ class SecondaryCacheSpec : ShouldSpec({ secondaryCacheRecordedRequests?.size shouldBe 1 // and: Secondary cache request 'expiry' parameter matches to the Prebid Cache 'cache.max.expiry' config property - val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + val secondaryCacheRequest = + objectMapper.readValue(secondaryCacheRecordedRequests!!.first().bodyAsString, RequestObject::class.java) secondaryCacheRequest.puts.size shouldBe 1 secondaryCacheRequest.puts[0].expiry shouldBe configCacheMaxExpiry } @@ -144,7 +153,7 @@ class SecondaryCacheSpec : ShouldSpec({ // and: Request object with set 'expiry' lower than configuration 'cache.min.expiry' val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = PrebidCacheUtil.getRandomUuid() + requestObject.puts[0].key = getRandomUuid() requestObject.puts[0].ttlseconds = null requestObject.puts[0].expiry = configCacheMinExpiry!! - 1 @@ -160,7 +169,8 @@ class SecondaryCacheSpec : ShouldSpec({ secondaryCacheRecordedRequests?.size shouldBe 1 // and: Secondary cache request 'expiry' parameter matches to the Prebid Cache 'cache.min.expiry' config property - val secondaryCacheRequest = PrebidCacheUtil.objectMapper.readValue(secondaryCacheRecordedRequests?.first()?.bodyAsString, RequestObject::class.java) + val secondaryCacheRequest = + objectMapper.readValue(secondaryCacheRecordedRequests!!.first().bodyAsString, RequestObject::class.java) secondaryCacheRequest.puts.size shouldBe 1 secondaryCacheRequest.puts[0].expiry shouldBe configCacheMinExpiry } diff --git a/src/test/kotlin/org/prebid/cache/functional/mapper/Mapper.kt b/src/test/kotlin/org/prebid/cache/functional/mapper/Mapper.kt new file mode 100644 index 0000000..da9c82d --- /dev/null +++ b/src/test/kotlin/org/prebid/cache/functional/mapper/Mapper.kt @@ -0,0 +1,6 @@ +package org.prebid.cache.functional.mapper + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule + +val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()) diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt index f42f470..4afba48 100644 --- a/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt @@ -7,7 +7,5 @@ enum class MediaType { JSON, XML, UNSUPPORTED; @JsonValue - fun getValue(): String { - return name.lowercase() - } + fun getValue(): String = name.lowercase() } diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt index 2688fef..3dcba72 100644 --- a/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt @@ -2,31 +2,33 @@ package org.prebid.cache.functional.model.request import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL +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.util.PrebidCacheUtil @JsonInclude(NON_NULL) data class PayloadTransfer( - var type: MediaType, - var key: String? = null, - var value: String, - var expiry: Long? = null, - var ttlseconds: Long? = null, - var prefix: String? = null + var type: MediaType, + var key: String? = null, + var value: String, + var expiry: Long? = null, + var ttlseconds: Long? = null, + var prefix: String? = null ) { companion object { - fun getDefaultJsonPayloadTransfer(): PayloadTransfer { - return PayloadTransfer(type = JSON, - value = PrebidCacheUtil.objectMapper.writeValueAsString(TransferValue.getDefaultJsonValue()), - expiry = 300) - } + fun getDefaultJsonPayloadTransfer(): PayloadTransfer = + PayloadTransfer( + type = JSON, + value = objectMapper.writeValueAsString(TransferValue.getDefaultJsonValue()), + expiry = 300 + ) - fun getDefaultXmlPayloadTransfer(): PayloadTransfer { - return PayloadTransfer(type = XML, - value = PrebidCacheUtil.objectMapper.writeValueAsString(TransferValue.getDefaultXmlValue()), - expiry = 300) - } + fun getDefaultXmlPayloadTransfer(): PayloadTransfer = + PayloadTransfer( + type = XML, + value = objectMapper.writeValueAsString(TransferValue.getDefaultXmlValue()), + expiry = 300 + ) } } diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt index d1c093f..cd0b0e2 100644 --- a/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt @@ -3,12 +3,10 @@ package org.prebid.cache.functional.model.request data class RequestObject(var puts: List) { companion object { - fun getDefaultJsonRequestObject(): RequestObject { - return RequestObject(puts = listOf(PayloadTransfer.getDefaultJsonPayloadTransfer())) - } + fun getDefaultJsonRequestObject(): RequestObject = + RequestObject(puts = listOf(PayloadTransfer.getDefaultJsonPayloadTransfer())) - fun getDefaultXmlRequestObject(): RequestObject { - return RequestObject(puts = listOf(PayloadTransfer.getDefaultXmlPayloadTransfer())) - } + fun getDefaultXmlRequestObject(): RequestObject = + RequestObject(puts = listOf(PayloadTransfer.getDefaultXmlPayloadTransfer())) } } diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/TransferValue.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/TransferValue.kt index c02305b..e76d603 100644 --- a/src/test/kotlin/org/prebid/cache/functional/model/request/TransferValue.kt +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/TransferValue.kt @@ -3,14 +3,14 @@ package org.prebid.cache.functional.model.request data class TransferValue(var adm: String, var width: Int? = null, var height: Int? = null) { companion object { - fun getDefaultJsonValue(): TransferValue { - return TransferValue(adm = "\\n
\\\"\\\"
\\n\\n\\n\\\"\\\"
\\n\",\n", + fun getDefaultJsonValue(): TransferValue = + TransferValue( + adm = "\\n
\\\"\\\"
\\n\\n\\n\\\"\\\"
\\n\",\n", width = 300, - height = 250) - } + height = 250 + ) - fun getDefaultXmlValue(): TransferValue { - return TransferValue(adm = "\\r\\n \\r\\n <\\/html>\\r\\n ]]>\\r\\n <\\/creativeCode>\\r\\n<\\/xml>\"") - } + fun getDefaultXmlValue(): TransferValue = + TransferValue(adm = "\\r\\n \\r\\n <\\/html>\\r\\n ]]>\\r\\n <\\/creativeCode>\\r\\n<\\/xml>\"") } } 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 513b8e6..0146f6e 100644 --- a/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt +++ b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt @@ -1,53 +1,69 @@ package org.prebid.cache.functional.service -import io.restassured.RestAssured.given -import io.restassured.builder.RequestSpecBuilder -import io.restassured.http.ContentType.JSON -import io.restassured.response.Response -import io.restassured.specification.RequestSpecification +import io.ktor.client.HttpClient +import io.ktor.client.call.receive +import io.ktor.client.engine.apache.Apache +import io.ktor.client.features.ClientRequestException +import io.ktor.client.features.HttpResponseValidator +import io.ktor.client.features.ServerResponseException +import io.ktor.client.features.defaultRequest +import io.ktor.client.features.json.JacksonSerializer +import io.ktor.client.features.json.JsonFeature +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.host +import io.ktor.client.request.parameter +import io.ktor.client.request.port +import io.ktor.client.request.post +import io.ktor.client.statement.HttpResponse +import io.ktor.http.ContentType.Application.Json +import io.ktor.http.HttpHeaders.ContentType +import io.ktor.http.HttpStatusCode.Companion.OK import org.prebid.cache.functional.model.request.RequestObject import org.prebid.cache.functional.model.response.ResponseObject -class PrebidCacheApi(prebidCacheUrl: String) { +class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) { - companion object { - private const val CACHE_ENDPOINT = "/cache" - private const val UUID_QUERY_PARAMETER = "uuid" - private const val SECONDARY_CACHE_QUERY_PARAMETER = "secondaryCache" + private val client = HttpClient(Apache) { + defaultRequest { + host = prebidCacheHost + port = prebidCachePort + header(ContentType, Json) + } + install(JsonFeature) { + serializer = JacksonSerializer() + } + HttpResponseValidator { + handleResponseException { exception -> + val clientException = exception as? ClientRequestException ?: return@handleResponseException + checkResponseStatusCode(clientException.response) + } + handleResponseException { exception -> + val serverException = exception as? ServerResponseException ?: return@handleResponseException + checkResponseStatusCode(serverException.response) + } + } } - private val requestSpecification: RequestSpecification + suspend fun getCache(uuid: String?): HttpResponse = + client.get(CACHE_ENDPOINT) { + if (uuid != null) parameter(UUID_QUERY_PARAMETER, uuid) + } - init { - requestSpecification = buildRequestSpecification(prebidCacheUrl) - } - - fun getCache(uuid: String?): Response { - val request = given(requestSpecification) - if (uuid != null) request.queryParam(UUID_QUERY_PARAMETER, uuid) - val response: Response = request.get(CACHE_ENDPOINT) + suspend fun postCache(requestObject: RequestObject, secondaryCache: String? = null): ResponseObject = + client.post(CACHE_ENDPOINT) { + if (secondaryCache != null) parameter(SECONDARY_CACHE_QUERY_PARAMETER, secondaryCache) + body = requestObject + } - checkResponseStatusCode(response) - return response + private suspend fun checkResponseStatusCode(response: HttpResponse) { + val statusCode = response.status.value + if (statusCode != OK.value) throw ApiException(statusCode, response.receive()) } - fun postCache(requestObject: RequestObject, secondaryCache: String? = null): ResponseObject { - val request = given(requestSpecification).body(requestObject) - if (secondaryCache != null) request.queryParam(SECONDARY_CACHE_QUERY_PARAMETER, secondaryCache) - val response: Response = request.post(CACHE_ENDPOINT) - - checkResponseStatusCode(response) - return response.`as`(ResponseObject::class.java) - } - - private fun checkResponseStatusCode(response: Response) { - val statusCode = response.statusCode - if (statusCode != 200) throw ApiException(statusCode, response.body.asString()) - } - - private fun buildRequestSpecification(prebidCacheUrl: String): RequestSpecification { - return RequestSpecBuilder().setBaseUri(prebidCacheUrl) - .setContentType(JSON) - .build() + companion object { + private const val CACHE_ENDPOINT = "/cache" + private const val UUID_QUERY_PARAMETER = "uuid" + private const val SECONDARY_CACHE_QUERY_PARAMETER = "secondaryCache" } } diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt index 048dafd..4cb45b9 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt @@ -17,17 +17,17 @@ abstract class ContainerDependencies { val network: Network = Network.newNetwork() val redisContainer: RedisContainer = RedisContainer(redisImageName).withNetwork(network) val aerospikeContainer: AerospikeContainer = AerospikeContainer(aerospikeImageName).withNetwork(network) - val webCacheContainer: WebCacheContainer = WebCacheContainer(mockServerImageVersion).withNetwork(network) as WebCacheContainer + val webCacheContainer: WebCacheContainer = + WebCacheContainer(mockServerImageVersion).withNetwork(network) as WebCacheContainer val prebidCacheContainerPool = PrebidCacheContainerPool(prebidCacheImageName) fun startCacheServerContainers() { Startables.deepStart(listOf(redisContainer, aerospikeContainer)) - .join() + .join() } - fun stopCacheServerContainers() { + fun stopCacheServerContainers() = listOf(redisContainer, aerospikeContainer).parallelStream() - .forEach { it.stop() } - } + .forEach { it.stop() } } } diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerStartable.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerStartable.kt index 277433d..3a5f70f 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerStartable.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerStartable.kt @@ -6,11 +6,7 @@ import io.kotest.core.listeners.ProjectListener @AutoScan object ContainerStartable : ProjectListener { - override suspend fun beforeProject() { - ContainerDependencies.startCacheServerContainers() - } + override suspend fun beforeProject() = ContainerDependencies.startCacheServerContainers() - override suspend fun afterProject() { - ContainerDependencies.stopCacheServerContainers() - } + override suspend fun afterProject() = ContainerDependencies.stopCacheServerContainers() } 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 8dc9038..459d684 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt @@ -7,67 +7,56 @@ import org.prebid.cache.functional.testcontainers.container.WebCacheContainer.Co class PrebidCacheContainerConfig(private val redisHost: String, private val aerospikeHost: String) { - fun getBaseRedisConfig(allowExternalUuid: String): Map { - return getBaseConfig(allowExternalUuid) + getRedisConfig() - } + fun getBaseRedisConfig(allowExternalUuid: String): Map = + getBaseConfig(allowExternalUuid) + getRedisConfig() - fun getBaseAerospikeConfig(allowExternalUuid: String): Map { - return getBaseConfig(allowExternalUuid) + getAerospikeConfig() - } + fun getBaseAerospikeConfig(allowExternalUuid: String): Map = + getBaseConfig(allowExternalUuid) + getAerospikeConfig() - fun getRedisConfig(): Map { - return mapOf( - "spring.redis.port" to RedisContainer.PORT.toString(), - "spring.redis.host" to redisHost, - "spring.redis.timeout" to "300" + fun getRedisConfig(): Map = + mapOf( + "spring.redis.port" to RedisContainer.PORT.toString(), + "spring.redis.host" to redisHost, + "spring.redis.timeout" to "300" ) - } - fun getAerospikeConfig(aerospikeNamespace: String = NAMESPACE): Map { - return mapOf( - "spring.aerospike.port" to AerospikeContainer.PORT.toString(), - "spring.aerospike.host" to aerospikeHost, - "spring.aerospike.cores" to "4", - "spring.aerospike.password" to "", - "spring.aerospike.first_backoff" to "300", - "spring.aerospike.max_backoff" to "1000", - "spring.aerospike.max_retry" to "3", - "spring.aerospike.namespace" to aerospikeNamespace + fun getAerospikeConfig(aerospikeNamespace: String = NAMESPACE): Map = + mapOf( + "spring.aerospike.port" to AerospikeContainer.PORT.toString(), + "spring.aerospike.host" to aerospikeHost, + "spring.aerospike.cores" to "4", + "spring.aerospike.password" to "", + "spring.aerospike.first_backoff" to "300", + "spring.aerospike.max_backoff" to "1000", + "spring.aerospike.max_retry" to "3", + "spring.aerospike.namespace" to aerospikeNamespace ) - } - fun getCacheExpiryConfig(minExpiry: String = "15", maxExpiry: String = "28800"): Map { - return mapOf( - "cache.min.expiry" to minExpiry, - "cache.max.expiry" to maxExpiry, - "cache.expiry.sec" to "300" + fun getCacheExpiryConfig(minExpiry: String = "15", maxExpiry: String = "28800"): Map = + mapOf( + "cache.min.expiry" to minExpiry, + "cache.max.expiry" to maxExpiry, + "cache.expiry.sec" to "300" ) - } - fun getCacheTimeoutConfig(timeoutMs: String): Map { - return mapOf("cache.timeout.ms" to timeoutMs) - } + fun getCacheTimeoutConfig(timeoutMs: String): Map = + mapOf("cache.timeout.ms" to timeoutMs) - fun getAerospikePreventUuidDuplicationConfig(preventUuidDuplication: String): Map { - return mapOf("spring.aerospike.prevent-u-u-i-d-duplication" to preventUuidDuplication) - } + fun getAerospikePreventUuidDuplicationConfig(preventUuidDuplication: String): Map = + mapOf("spring.aerospike.prevent-u-u-i-d-duplication" to preventUuidDuplication) - fun getSecondaryCacheConfig(secondaryCacheUri: String): Map { - return mapOf( - "cache.secondary_cache_path" to WEB_CACHE_PATH, - "cache.secondary-uris" to secondaryCacheUri + fun getSecondaryCacheConfig(secondaryCacheUri: String): Map = + mapOf( + "cache.secondary_cache_path" to WEB_CACHE_PATH, + "cache.secondary-uris" to secondaryCacheUri ) - } - private fun getBaseConfig(allowExternalUuid: String): Map { - return getCachePrefixConfig() + getCacheExpiryConfig() + getAllowExternalUuidConfig(allowExternalUuid) - } + private fun getBaseConfig(allowExternalUuid: String): Map = + getCachePrefixConfig() + getCacheExpiryConfig() + getAllowExternalUuidConfig(allowExternalUuid) + + getCacheTimeoutConfig("500") - private fun getCachePrefixConfig(): Map { - return mapOf("cache.prefix" to "prebid_") - } + private fun getCachePrefixConfig(): Map = mapOf("cache.prefix" to "prebid_") - private fun getAllowExternalUuidConfig(allowExternalUuid: String): Map { - return mapOf("cache.allow-external-u-u-i-d" to allowExternalUuid) - } + private fun getAllowExternalUuidConfig(allowExternalUuid: String): Map = + mapOf("cache.allow-external-u-u-i-d" to allowExternalUuid) } diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt index 514978c..cdfc036 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt @@ -4,11 +4,6 @@ import org.prebid.cache.functional.testcontainers.container.PrebidCacheContainer class PrebidCacheContainerPool(private val containerImageName: String) { - companion object { - private val MAX_CONTAINER_COUNT: Int = (System.getProperty("max.containers.count") ?: "3").toInt() - private val prebidCacheContainerMap: MutableMap, PrebidCacheContainer> = mutableMapOf() - } - fun getPrebidCacheContainer(config: Map): PrebidCacheContainer { if (prebidCacheContainerMap.size >= MAX_CONTAINER_COUNT) { val oldestContainerConfig = @@ -30,4 +25,9 @@ class PrebidCacheContainerPool(private val containerImageName: String) { if (prebidCacheContainerMap.containsKey(config)) prebidCacheContainerMap.getValue(config).stop() prebidCacheContainerMap.remove(config) } + + companion object { + private val MAX_CONTAINER_COUNT: Int = (System.getProperty("max.containers.count") ?: "3").toInt() + private val prebidCacheContainerMap: MutableMap, PrebidCacheContainer> = mutableMapOf() + } } 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 0978564..c56ea58 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,11 +1,14 @@ package org.prebid.cache.functional.testcontainers.client import org.mockserver.client.MockServerClient +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.prebid.cache.functional.testcontainers.container.WebCacheContainer.Companion.WEB_CACHE_PATH +import org.springframework.http.HttpMethod.POST +import org.springframework.http.HttpStatus.OK class WebCacheContainerClient(mockServerHost: String, mockServerPort: Int) { @@ -16,23 +19,19 @@ class WebCacheContainerClient(mockServerHost: String, mockServerPort: Int) { initResponse() } - fun getRecordedRequests(uuidKey: String): Array? { - return mockServerClient.retrieveRecordedRequests(getSecondaryCacheRequest(uuidKey)) - } + fun getRecordedRequests(uuidKey: String): Array? = + mockServerClient.retrieveRecordedRequests(getSecondaryCacheRequest(uuidKey)) - private fun getSecondaryCacheRequest(): HttpRequest { - return request().withMethod("POST") - .withPath("/$WEB_CACHE_PATH") - } + private fun getSecondaryCacheRequest(): HttpRequest = + request().withMethod(POST.name) + .withPath("/$WEB_CACHE_PATH") - private fun getSecondaryCacheRequest(uuidKey: String): HttpRequest { - return request().withMethod("POST") - .withPath("/$WEB_CACHE_PATH") - .withBody(jsonPath("\$.puts[?(@.key == '$uuidKey')]")) - } + private fun getSecondaryCacheRequest(uuidKey: String): HttpRequest = + request().withMethod(POST.name) + .withPath("/$WEB_CACHE_PATH") + .withBody(jsonPath("\$.puts[?(@.key == '$uuidKey')]")) - private fun initResponse() { + private fun initResponse(): Array? = mockServerClient.`when`(getSecondaryCacheRequest()) - .respond(response().withStatusCode(200)) - } -} \ No newline at end of file + .respond(response().withStatusCode(OK.value())) +} diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/AerospikeContainer.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/AerospikeContainer.kt index 0de7e2b..91cd7bf 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/AerospikeContainer.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/AerospikeContainer.kt @@ -4,20 +4,16 @@ import org.testcontainers.containers.GenericContainer class AerospikeContainer(imageName: String) : GenericContainer(imageName) { - companion object { - const val PORT = 3000 - const val NAMESPACE = "prebid_cache" - } - init { withNamespace() } - fun getContainerHost(): String { - return networkAliases.first() - } + fun getContainerHost(): String = networkAliases.first() + + private fun withNamespace(): AerospikeContainer = withEnv(mapOf("NAMESPACE" to NAMESPACE)) - private fun withNamespace(): AerospikeContainer { - return withEnv(mapOf("NAMESPACE" to NAMESPACE)) + companion object { + const val PORT = 3000 + const val NAMESPACE = "prebid_cache" } } diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt index fed510b..bdb2d1f 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt @@ -4,16 +4,7 @@ import org.prebid.cache.functional.testcontainers.ContainerDependencies import org.testcontainers.containers.GenericContainer class PrebidCacheContainer(imageName: String, config: Map) : - GenericContainer(imageName) { - - companion object { - private const val PORT = 8080 - private const val DEBUG_PORT = 8000 - private const val FIXED_EXPOSED_APPLICATION_PORT = 49100 - private const val FIXED_EXPOSED_DEBUG_PORT = 49101 - - private val USE_FIXED_PORTS = (System.getProperty("useFixedContainerPorts") ?: "false").toBoolean() - } + GenericContainer(imageName) { init { withNetwork(ContainerDependencies.network) @@ -23,17 +14,10 @@ class PrebidCacheContainer(imageName: String, config: Map) : withConfig(config) } - fun getHostUri(): String { - return "http://$host:${getHostPort()}" - } + fun getHostPort(): Int = getMappedPort(PORT) - private fun getHostPort(): Int { - return getMappedPort(PORT) - } - - private fun withConfig(config: Map): PrebidCacheContainer { - return withEnv(normalizeProperties(config)) - } + private fun withConfig(config: Map): PrebidCacheContainer = + withEnv(normalizeProperties(config)) private fun withFixedExposedPorts() { if (USE_FIXED_PORTS) { @@ -42,17 +26,24 @@ class PrebidCacheContainer(imageName: String, config: Map) : } } - private fun withDebug(): PrebidCacheContainer { - return withEnv("JAVA_TOOL_OPTIONS", - "-agentlib:jdwp=transport=dt_socket,address=*:$DEBUG_PORT,server=y,suspend=n") - } + private fun withDebug(): PrebidCacheContainer = withEnv( + "JAVA_TOOL_OPTIONS", + "-agentlib:jdwp=transport=dt_socket,address=*:$DEBUG_PORT,server=y,suspend=n" + ) - private fun normalizeProperties(config: Map): Map { - return config.mapKeys { normalizeProperty(it.key) } - } + private fun normalizeProperties(config: Map): Map = + config.mapKeys { normalizeProperty(it.key) } - private fun normalizeProperty(property: String): String { - return property.replace(".", "_") - .replace("-", "") + private fun normalizeProperty(property: String): String = + property.replace(".", "_") + .replace("-", "") + + companion object { + private const val PORT = 8080 + private const val DEBUG_PORT = 8000 + private const val FIXED_EXPOSED_APPLICATION_PORT = 49100 + private const val FIXED_EXPOSED_DEBUG_PORT = 49101 + + private val USE_FIXED_PORTS = (System.getProperty("useFixedContainerPorts") ?: "false").toBoolean() } } diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/RedisContainer.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/RedisContainer.kt index 65a8b5d..0254251 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/RedisContainer.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/RedisContainer.kt @@ -4,11 +4,9 @@ import org.testcontainers.containers.GenericContainer class RedisContainer(imageName: String) : GenericContainer(imageName) { + fun getContainerHost(): String = networkAliases.first() + companion object { const val PORT = 6379 } - - fun getContainerHost(): String { - return networkAliases.first() - } } diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/WebCacheContainer.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/WebCacheContainer.kt index 46dfead..8299e3a 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/WebCacheContainer.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/WebCacheContainer.kt @@ -4,12 +4,10 @@ import org.testcontainers.containers.MockServerContainer class WebCacheContainer(mockServerImageVersion: String) : MockServerContainer(mockServerImageVersion) { + fun getContainerHost(): String = networkAliases.first() + companion object { const val PORT = MockServerContainer.PORT const val WEB_CACHE_PATH = "cache" } - - fun getContainerHost(): String { - return networkAliases.first() - } } diff --git a/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt b/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt index fde54cf..1e92691 100644 --- a/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt +++ b/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt @@ -1,22 +1,11 @@ package org.prebid.cache.functional.util -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule -import java.util.* +import java.util.UUID -class PrebidCacheUtil { +fun getRandomUuid(): String = UUID.randomUUID().toString() - companion object { - val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()) - - fun getRandomUuid(): String { - return UUID.randomUUID().toString() - } - - fun getRandomString(length: Int = 16): String { - val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') - return (1..length).map { allowedChars.random() } - .joinToString("") - } - } +fun getRandomString(length: Int = 16): String { + val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') + return (1..length).map { allowedChars.random() } + .joinToString("") } From d11c00da0903d3329f6ca44c56edf7cc55ccb3fd Mon Sep 17 00:00:00 2001 From: Mykyta Hupalo Date: Sat, 9 Apr 2022 11:44:09 +0300 Subject: [PATCH 03/11] Add functional tests to cover proxy cache host parameter (#57) Co-authored-by: mhupalo --- .../cache/functional/ProxyCacheHostSpec.kt | 157 ++++++++++++++++++ .../cache/functional/SecondaryCacheSpec.kt | 17 +- .../functional/service/PrebidCacheApi.kt | 4 +- .../PrebidCacheContainerConfig.kt | 6 + .../client/WebCacheContainerClient.kt | 37 ++++- 5 files changed, 208 insertions(+), 13 deletions(-) create mode 100644 src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt 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())) } From 1e1b7f46e61ec3c991c212dd48ec6cf353e5dfbb Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko Date: Wed, 4 May 2022 14:55:56 +0200 Subject: [PATCH 04/11] Loosen timeouts --- .../kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt | 2 +- .../functional/testcontainers/PrebidCacheContainerConfig.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt index 9064ce7..8e09ae6 100644 --- a/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt @@ -71,7 +71,7 @@ class ProxyCacheHostSpec : ShouldSpec({ // 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\"" + exception.responseBody shouldContain "\"message\":\"$cacheHost: Temporary failure in name resolution\"" } } 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 1d2c919..06e9fdb 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt @@ -59,7 +59,8 @@ class PrebidCacheContainerConfig(private val redisHost: String, private val aero private fun getBaseConfig(allowExternalUuid: String): Map = getCachePrefixConfig() + getCacheExpiryConfig() + getAllowExternalUuidConfig(allowExternalUuid) + - getCacheTimeoutConfig("500") + getCacheTimeoutConfig("2500") + private fun getCachePrefixConfig(): Map = mapOf("cache.prefix" to "prebid_") From 6615716b70707a0334c57e7dd1ea468d2818aa6f Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko Date: Wed, 4 May 2022 14:59:29 +0200 Subject: [PATCH 05/11] Add workflow for functional tests --- .github/workflows/pr-functional-tests.yml | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/pr-functional-tests.yml diff --git a/.github/workflows/pr-functional-tests.yml b/.github/workflows/pr-functional-tests.yml new file mode 100644 index 0000000..fba38aa --- /dev/null +++ b/.github/workflows/pr-functional-tests.yml @@ -0,0 +1,39 @@ +name: Functional tests + +on: + pull_request: + paths-ignore: + - 'docs/**' + - '.github/**' + branches: + - master + release: + types: + - created + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + java: [ 11 ] + + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + + - name: Cache Maven dependendcies + uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Build with Maven + run: mvn -B verify -DskipUnitTests=true --file extra/pom.xml From b6485aef42947a474a0acd90e5d79a5230c16fe4 Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko Date: Wed, 4 May 2022 15:02:10 +0200 Subject: [PATCH 06/11] Fix workflow derp --- .github/workflows/pr-functional-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-functional-tests.yml b/.github/workflows/pr-functional-tests.yml index fba38aa..bbf82de 100644 --- a/.github/workflows/pr-functional-tests.yml +++ b/.github/workflows/pr-functional-tests.yml @@ -36,4 +36,4 @@ jobs: ${{ runner.os }}-maven- - name: Build with Maven - run: mvn -B verify -DskipUnitTests=true --file extra/pom.xml + run: mvn -B verify -DskipUnitTests=true --file pom.xml From a2bd2e57d681ffc35850c2865e1ac99570b8591b Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko Date: Thu, 5 May 2022 10:30:07 +0200 Subject: [PATCH 07/11] Update Kotlin dependencies --- pom.xml | 28 ++++++++---- .../cache/functional/AerospikeCacheSpec.kt | 4 +- .../cache/functional/GeneralCacheSpec.kt | 8 ++-- .../cache/functional/ProxyCacheHostSpec.kt | 6 +-- .../prebid/cache/functional/mapper/Mapper.kt | 2 +- .../functional/service/PrebidCacheApi.kt | 43 ++++++++----------- .../testcontainers/ContainerDependencies.kt | 2 +- 7 files changed, 48 insertions(+), 45 deletions(-) diff --git a/pom.xml b/pom.xml index a3603cf..fe0e715 100644 --- a/pom.xml +++ b/pom.xml @@ -73,12 +73,12 @@ 2.5.3 - 5.1.0 - 1.6.10 - 1.16.3 - 5.12.0 - 1.6.8 - 2.9.10 + 5.3.0 + 1.6.21 + 1.17.1 + 5.13.2 + 2.0.1 + 2.13.2 0.39.1 @@ -261,13 +261,25 @@
io.ktor - ktor-client-apache + ktor-client-core-jvm ${ktor.version} test io.ktor - ktor-client-jackson + ktor-client-apache-jvm + ${ktor.version} + test + + + io.ktor + ktor-client-content-negotiation-jvm + ${ktor.version} + test + + + io.ktor + ktor-serialization-jackson-jvm ${ktor.version} test diff --git a/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt index 091606b..285252d 100644 --- a/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt @@ -6,7 +6,7 @@ import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain -import io.ktor.client.statement.readText +import io.ktor.client.statement.bodyAsText import org.prebid.cache.functional.BaseSpec.Companion.prebidCacheConfig import org.prebid.cache.functional.mapper.objectMapper import org.prebid.cache.functional.model.request.PayloadTransfer @@ -150,7 +150,7 @@ class AerospikeCacheSpec : ShouldSpec({ // and: Cache record was updated in Aerospike with a second request object payload val getCacheResponse = prebidCacheApi.getCache(responseObject.responses[0].uuid) - val responseTransferValue = objectMapper.readValue(getCacheResponse.readText(), TransferValue::class.java) + val responseTransferValue = objectMapper.readValue(getCacheResponse.bodyAsText(), TransferValue::class.java) assertSoftly { responseTransferValue.adm shouldBe requestTransferValue.adm diff --git a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt index 0d659ce..8df2af3 100644 --- a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt @@ -5,7 +5,7 @@ import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain -import io.ktor.client.statement.readText +import io.ktor.client.statement.bodyAsText import io.ktor.http.contentType import org.prebid.cache.functional.mapper.objectMapper import org.prebid.cache.functional.model.request.MediaType.UNSUPPORTED @@ -158,7 +158,7 @@ class GeneralCacheSpec : ShouldSpec({ getCacheResponse.contentType()?.contentSubtype shouldBe requestObject.puts[0].type.getValue() // and: transfer value is returned - val responseTransferValue = objectMapper.readValue(getCacheResponse.readText(), TransferValue::class.java) + val responseTransferValue = objectMapper.readValue(getCacheResponse.bodyAsText(), TransferValue::class.java) assertSoftly { responseTransferValue.adm shouldBe requestTransferValue.adm @@ -183,7 +183,7 @@ class GeneralCacheSpec : ShouldSpec({ getCacheResponse.contentType()?.contentSubtype shouldBe requestObject.puts[0].type.getValue() // and: transfer value is returned - val responseTransferValue = objectMapper.readValue(getCacheResponse.readText(), TransferValue::class.java) + val responseTransferValue = objectMapper.readValue(getCacheResponse.bodyAsText(), TransferValue::class.java) assertSoftly { responseTransferValue.adm shouldBe requestTransferValue.adm @@ -204,6 +204,6 @@ class GeneralCacheSpec : ShouldSpec({ val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid) // then: transfer value as a plain String is returned - getCacheResponse.readText() shouldBe requestObject.puts[0].value + getCacheResponse.bodyAsText() shouldBe requestObject.puts[0].value } }) diff --git a/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt index 8e09ae6..606400c 100644 --- a/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt @@ -7,7 +7,7 @@ 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.client.statement.bodyAsText import io.ktor.http.contentType import org.mockserver.model.MediaType.APPLICATION_JSON_UTF_8 import org.mockserver.model.MediaType.APPLICATION_XML @@ -122,7 +122,7 @@ class ProxyCacheHostSpec : ShouldSpec({ val response = prebidCacheApi.getCache(getRandomUuid(), proxyCacheHost) // then: PBC response body should be equal to proxy cache host response body - response.readText() shouldBe cacheHostResponseBody + response.bodyAsText() shouldBe cacheHostResponseBody } should("return a response body as a JSON or XML object requested from proxy cache host") { @@ -145,7 +145,7 @@ class ProxyCacheHostSpec : ShouldSpec({ response.contentType()?.contentSubtype shouldBe prebidCacheResponseMediaType.getValue() // and: transfer value is returned - val responseTransferValue = objectMapper.readValue(response.readText(), TransferValue::class.java) + val responseTransferValue = objectMapper.readValue(response.bodyAsText(), TransferValue::class.java) assertSoftly { responseTransferValue.adm shouldBe proxyCacheResponseBody.adm diff --git a/src/test/kotlin/org/prebid/cache/functional/mapper/Mapper.kt b/src/test/kotlin/org/prebid/cache/functional/mapper/Mapper.kt index da9c82d..369f494 100644 --- a/src/test/kotlin/org/prebid/cache/functional/mapper/Mapper.kt +++ b/src/test/kotlin/org/prebid/cache/functional/mapper/Mapper.kt @@ -3,4 +3,4 @@ package org.prebid.cache.functional.mapper import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule -val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()) +val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule.Builder().build()) 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 6ca9606..b164ffe 100644 --- a/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt +++ b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt @@ -1,46 +1,42 @@ package org.prebid.cache.functional.service import io.ktor.client.HttpClient -import io.ktor.client.call.receive +import io.ktor.client.call.body import io.ktor.client.engine.apache.Apache -import io.ktor.client.features.ClientRequestException -import io.ktor.client.features.HttpResponseValidator -import io.ktor.client.features.ServerResponseException -import io.ktor.client.features.defaultRequest -import io.ktor.client.features.json.JacksonSerializer -import io.ktor.client.features.json.JsonFeature +import io.ktor.client.plugins.HttpResponseValidator +import io.ktor.client.plugins.ResponseException +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest import io.ktor.client.request.get import io.ktor.client.request.header -import io.ktor.client.request.host import io.ktor.client.request.parameter -import io.ktor.client.request.port import io.ktor.client.request.post +import io.ktor.client.request.setBody import io.ktor.client.statement.HttpResponse +import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType.Application.Json import io.ktor.http.HttpHeaders.ContentType -import io.ktor.http.HttpStatusCode.Companion.OK +import io.ktor.serialization.jackson.jackson import org.prebid.cache.functional.model.request.RequestObject import org.prebid.cache.functional.model.response.ResponseObject class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) { private val client = HttpClient(Apache) { + expectSuccess = true defaultRequest { host = prebidCacheHost port = prebidCachePort header(ContentType, Json) } - install(JsonFeature) { - serializer = JacksonSerializer() + install(ContentNegotiation) { + jackson() } HttpResponseValidator { - handleResponseException { exception -> - val clientException = exception as? ClientRequestException ?: return@handleResponseException - checkResponseStatusCode(clientException.response) - } - handleResponseException { exception -> - val serverException = exception as? ServerResponseException ?: return@handleResponseException - checkResponseStatusCode(serverException.response) + handleResponseExceptionWithRequest { exception, _ -> + val clientException = exception as? ResponseException ?: return@handleResponseExceptionWithRequest + val statusCode = clientException.response.status + throw ApiException(statusCode.value, clientException.response.bodyAsText()) } } } @@ -54,13 +50,8 @@ class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) { suspend fun postCache(requestObject: RequestObject, secondaryCache: String? = null): ResponseObject = client.post(CACHE_ENDPOINT) { if (secondaryCache != null) parameter(SECONDARY_CACHE_QUERY_PARAMETER, secondaryCache) - body = requestObject - } - - private suspend fun checkResponseStatusCode(response: HttpResponse) { - val statusCode = response.status.value - if (statusCode != OK.value) throw ApiException(statusCode, response.receive()) - } + setBody(requestObject) + }.body() companion object { private const val CACHE_ENDPOINT = "/cache" diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt index 4cb45b9..4520738 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/ContainerDependencies.kt @@ -12,7 +12,7 @@ abstract class ContainerDependencies { private const val redisImageName = "redis:6.2.6-alpine" private const val aerospikeImageName = "aerospike:ce-5.7.0.11" private const val prebidCacheImageName = "prebid-cache:latest" - private const val mockServerImageVersion = "5.12.0" + private const val mockServerImageVersion = "5.13.2" val network: Network = Network.newNetwork() val redisContainer: RedisContainer = RedisContainer(redisImageName).withNetwork(network) From 40d3e42496045ff1cf67a40637d3fc693e4656de Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko Date: Thu, 5 May 2022 11:13:00 +0200 Subject: [PATCH 08/11] Bump maven surefire version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fe0e715..d527e01 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ UTF-8 3.0.1 3.0.0 - 2.22.1 + 3.0.0-M6 3.8.0 2.1.2.RELEASE 6.0.1.RELEASE From 801959dad23cb724af5da0c8a00fb39cab1fbca0 Mon Sep 17 00:00:00 2001 From: mhupalo Date: Wed, 11 May 2022 17:46:47 +0300 Subject: [PATCH 09/11] Resolve PR remarks --- .../cache/functional/AerospikeCacheSpec.kt | 28 +++++-------- .../org/prebid/cache/functional/BaseSpec.kt | 5 +-- .../cache/functional/GeneralCacheSpec.kt | 15 +++---- .../cache/functional/ProxyCacheHostSpec.kt | 2 +- .../prebid/cache/functional/RedisCacheSpec.kt | 21 ++++------ .../cache/functional/SecondaryCacheSpec.kt | 42 ++++++++++--------- .../model/request/PayloadTransfer.kt | 2 +- .../functional/model/request/RequestObject.kt | 3 ++ .../PrebidCacheContainerPool.kt | 5 ++- .../container/PrebidCacheContainer.kt | 2 +- .../cache/functional/util/PrebidCacheUtil.kt | 5 +-- 11 files changed, 60 insertions(+), 70 deletions(-) diff --git a/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt index 285252d..fc02c88 100644 --- a/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt @@ -70,17 +70,15 @@ class AerospikeCacheSpec : ShouldSpec({ // and: First request object with set UUID val uuid = getRandomUuid() - val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() - xmlPayloadTransfer.key = uuid - val requestObject = RequestObject(listOf(xmlPayloadTransfer)) + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = uuid } + val requestObject = RequestObject.of(xmlPayloadTransfer) // and: First request object is saved to Aerospike cache prebidCacheApi.postCache(requestObject) // and: Second request object with already existing UUID is prepared - val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() - jsonPayloadTransfer.key = uuid - val secondRequestObject = RequestObject(listOf(jsonPayloadTransfer)) + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = uuid } + val secondRequestObject = RequestObject.of(jsonPayloadTransfer) // when: POST cache endpoint is called for the second time val exception = shouldThrowExactly { prebidCacheApi.postCache(secondRequestObject) } @@ -100,11 +98,9 @@ class AerospikeCacheSpec : ShouldSpec({ ) // and: Request object with 2 payload transfers and set UUIDs is prepared - val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() - val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() - xmlPayloadTransfer.key = getRandomUuid() - jsonPayloadTransfer.key = getRandomUuid() - val requestObject = RequestObject(listOf(xmlPayloadTransfer, jsonPayloadTransfer)) + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = getRandomUuid() } + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = getRandomUuid() } + val requestObject = RequestObject.of(xmlPayloadTransfer, jsonPayloadTransfer) // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject) @@ -127,17 +123,15 @@ class AerospikeCacheSpec : ShouldSpec({ // and: First request object val uuid = getRandomUuid() - val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() - xmlPayloadTransfer.key = uuid - val requestObject = RequestObject(listOf(xmlPayloadTransfer)) + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = uuid } + val requestObject = RequestObject.of(xmlPayloadTransfer) // and: First request object is saved to Aerospike cache prebidCacheApi.postCache(requestObject) // and: Second request object with already existing UUID is prepared - val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() - jsonPayloadTransfer.key = uuid - val secondRequestObject = RequestObject(listOf(jsonPayloadTransfer)) + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = uuid } + val secondRequestObject = RequestObject.of(jsonPayloadTransfer) val requestTransferValue = objectMapper.readValue(secondRequestObject.puts[0].value, TransferValue::class.java) // when: POST cache endpoint is called for the second time diff --git a/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt b/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt index fcafd88..6fa3529 100644 --- a/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt @@ -13,9 +13,8 @@ abstract class BaseSpec { ) fun getPrebidCacheApi(config: Map = prebidCacheConfig.getBaseRedisConfig("false")): PrebidCacheApi { - val prebidCacheContainer = - ContainerDependencies.prebidCacheContainerPool.getPrebidCacheContainer(config) - return PrebidCacheApi(prebidCacheContainer.host, prebidCacheContainer.getHostPort()) + return ContainerDependencies.prebidCacheContainerPool.getPrebidCacheContainer(config) + .let { container -> PrebidCacheApi(container.host, container.getHostPort()) } } } } diff --git a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt index 8df2af3..6fa64f5 100644 --- a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt @@ -33,8 +33,7 @@ class GeneralCacheSpec : ShouldSpec({ should("throw an exception when payload transfer key is given and allow_external_UUID=false") { // given: Request object with set payload transfer key - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = getRandomUuid() + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = getRandomUuid() } // when: POST cache endpoint is called val exception = shouldThrowExactly { BaseSpec.getPrebidCacheApi().postCache(requestObject) } @@ -51,8 +50,7 @@ class GeneralCacheSpec : ShouldSpec({ val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("true")) // and: Request object with set payload transfer key not in UUID format - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = getRandomUuid() + "*" + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = getRandomUuid() + "*" } // when: POST cache endpoint is called val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } @@ -69,8 +67,7 @@ class GeneralCacheSpec : ShouldSpec({ val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("true")) // and: Request object with set empty payload transfer key - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = "" + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = "" } // when: POST cache endpoint is called val exception = shouldThrowExactly { prebidCacheApi.postCache(requestObject) } @@ -84,8 +81,7 @@ class GeneralCacheSpec : ShouldSpec({ should("throw an exception when payload object with unsupported media type is fetched from repository") { // given: Request object with set unsupported media type - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].type = UNSUPPORTED + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].type = UNSUPPORTED } // and: POST cache endpoint is called val response = BaseSpec.getPrebidCacheApi().postCache(requestObject) @@ -194,8 +190,7 @@ class GeneralCacheSpec : ShouldSpec({ should("return the same String transfer value which was saved to cache") { // given: Request object with set transfer value as plain String - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].value = getRandomString() + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].value = getRandomString() } // and: POST cache endpoint is called val postResponse = BaseSpec.getPrebidCacheApi().postCache(requestObject) diff --git a/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt index 606400c..3acb008 100644 --- a/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt @@ -110,7 +110,7 @@ class ProxyCacheHostSpec : ShouldSpec({ webCacheContainerClient.getProxyCacheHostRecordedRequestCount() shouldBe initialProxyCacheHostRequestCount + 1 val proxyCacheHostRequest = webCacheContainerClient.getProxyCacheHostRecordedRequests()!!.last() - proxyCacheHostRequest.queryStringParameters?.containsEntry("uuid", requestUuid) + proxyCacheHostRequest.queryStringParameters?.containsEntry("uuid", requestUuid) shouldBe true } should("return a response body as a plain String requested from proxy cache host") { diff --git a/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt index b68a194..3feff70 100644 --- a/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt @@ -16,7 +16,7 @@ import org.prebid.cache.functional.testcontainers.ContainerDependencies import org.prebid.cache.functional.util.getRandomUuid import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR import org.springframework.http.HttpStatus.NOT_FOUND -import java.util.UUID +import java.util.* class RedisCacheSpec : ShouldSpec({ @@ -72,11 +72,9 @@ class RedisCacheSpec : ShouldSpec({ should("return back two random UUIDs when allow_external_UUID=false and 2 payload transfers were successfully cached") { // given: Request object - val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() - val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() - xmlPayloadTransfer.key = null - jsonPayloadTransfer.key = null - val requestObject = RequestObject(listOf(xmlPayloadTransfer, jsonPayloadTransfer)) + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = null } + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = null } + val requestObject = RequestObject.of(xmlPayloadTransfer, jsonPayloadTransfer) // when: POST cache endpoint is called val responseObject: ResponseObject = BaseSpec.getPrebidCacheApi().postCache(requestObject) @@ -93,8 +91,7 @@ class RedisCacheSpec : ShouldSpec({ val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig("true")) // and: Request object with set payload transfer UUID key - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = getRandomUuid() + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = getRandomUuid() } // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject) @@ -110,11 +107,9 @@ class RedisCacheSpec : ShouldSpec({ val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig("true")) // and: Request object with set 2 payload transfers - val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer() - val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer() - xmlPayloadTransfer.key = getRandomUuid() - jsonPayloadTransfer.key = getRandomUuid() - val requestObject = RequestObject(listOf(xmlPayloadTransfer, jsonPayloadTransfer)) + val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = getRandomUuid() } + val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = getRandomUuid() } + val requestObject = RequestObject.of(xmlPayloadTransfer, jsonPayloadTransfer) // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject) diff --git a/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt index 12c3658..0f1e30f 100644 --- a/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt +++ b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt @@ -46,8 +46,7 @@ class SecondaryCacheSpec : ShouldSpec({ should("send a request to secondary cache when secondary cache is configured and secondaryCache query parameter is given on request") { // given: Request object with set payload UUID key - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = getRandomUuid() + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = getRandomUuid() } // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") @@ -62,7 +61,8 @@ class SecondaryCacheSpec : ShouldSpec({ secondaryCacheRecordedRequests?.size shouldBe 1 // and: Request contained secondaryCache=yes query parameter - secondaryCacheRecordedRequests!!.first().queryStringParameters?.containsEntry("secondaryCache", "yes") + secondaryCacheRecordedRequests!!.first().queryStringParameters + ?.containsEntry("secondaryCache", "yes") shouldBe true // and: Secondary cache request body matched to the Prebid Cache request object val secondaryCacheRequest = @@ -72,10 +72,11 @@ class SecondaryCacheSpec : ShouldSpec({ should("set cache expiry equals to request 'ttlseconds' when ttlseconds parameter is given") { // given: Request object with set 'ttlseconds' parameter - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = getRandomUuid() - requestObject.puts[0].ttlseconds = 400 - requestObject.puts[0].expiry = 300 + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { + puts[0].key = getRandomUuid() + puts[0].ttlseconds = 400 + puts[0].expiry = 300 + } // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") @@ -98,10 +99,11 @@ class SecondaryCacheSpec : ShouldSpec({ should("set cache expiry from 'cache.expiry.sec' configuration property when request 'ttlseconds' and 'expiry' are absent'") { // given: Request object with absent 'ttlseconds' and 'expiry' - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = getRandomUuid() - requestObject.puts[0].ttlseconds = null - requestObject.puts[0].expiry = null + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { + puts[0].key = getRandomUuid() + puts[0].ttlseconds = null + puts[0].expiry = null + } // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") @@ -127,10 +129,11 @@ class SecondaryCacheSpec : ShouldSpec({ val configCacheMaxExpiry = specPrebidCacheConfig["cache.max.expiry"]?.toLong() // and: Request object with set 'expiry' higher than configuration 'cache.max.expiry' - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = getRandomUuid() - requestObject.puts[0].ttlseconds = null - requestObject.puts[0].expiry = configCacheMaxExpiry!! + 1 + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { + puts[0].key = getRandomUuid() + puts[0].ttlseconds = null + puts[0].expiry = configCacheMaxExpiry!! + 1 + } // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") @@ -156,10 +159,11 @@ class SecondaryCacheSpec : ShouldSpec({ val configCacheMinExpiry = specPrebidCacheConfig["cache.min.expiry"]?.toLong() // and: Request object with set 'expiry' lower than configuration 'cache.min.expiry' - val requestObject = RequestObject.getDefaultJsonRequestObject() - requestObject.puts[0].key = getRandomUuid() - requestObject.puts[0].ttlseconds = null - requestObject.puts[0].expiry = configCacheMinExpiry!! - 1 + val requestObject = RequestObject.getDefaultJsonRequestObject().apply { + puts[0].key = getRandomUuid() + puts[0].ttlseconds = null + puts[0].expiry = configCacheMinExpiry!! - 1 + } // when: POST cache endpoint is called val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject, "no") diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt index 3dcba72..f7a932f 100644 --- a/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt @@ -9,8 +9,8 @@ import org.prebid.cache.functional.model.request.MediaType.XML @JsonInclude(NON_NULL) data class PayloadTransfer( var type: MediaType, - var key: String? = null, var value: String, + var key: String? = null, var expiry: Long? = null, var ttlseconds: Long? = null, var prefix: String? = null diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt index cd0b0e2..2d08fb8 100644 --- a/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt +++ b/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt @@ -3,6 +3,9 @@ package org.prebid.cache.functional.model.request data class RequestObject(var puts: List) { companion object { + fun of(vararg puts: PayloadTransfer): RequestObject = + RequestObject(puts = puts.asList()) + fun getDefaultJsonRequestObject(): RequestObject = RequestObject(puts = listOf(PayloadTransfer.getDefaultJsonPayloadTransfer())) diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt index cdfc036..ce29192 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerPool.kt @@ -4,6 +4,8 @@ import org.prebid.cache.functional.testcontainers.container.PrebidCacheContainer class PrebidCacheContainerPool(private val containerImageName: String) { + private val prebidCacheContainerMap: MutableMap, PrebidCacheContainer> = mutableMapOf() + fun getPrebidCacheContainer(config: Map): PrebidCacheContainer { if (prebidCacheContainerMap.size >= MAX_CONTAINER_COUNT) { val oldestContainerConfig = @@ -27,7 +29,6 @@ class PrebidCacheContainerPool(private val containerImageName: String) { } companion object { - private val MAX_CONTAINER_COUNT: Int = (System.getProperty("max.containers.count") ?: "3").toInt() - private val prebidCacheContainerMap: MutableMap, PrebidCacheContainer> = mutableMapOf() + private val MAX_CONTAINER_COUNT: Int = System.getProperty("max.containers.count")?.toInt() ?: 3 } } diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt index bdb2d1f..d4e13f4 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/container/PrebidCacheContainer.kt @@ -44,6 +44,6 @@ class PrebidCacheContainer(imageName: String, config: Map) : private const val FIXED_EXPOSED_APPLICATION_PORT = 49100 private const val FIXED_EXPOSED_DEBUG_PORT = 49101 - private val USE_FIXED_PORTS = (System.getProperty("useFixedContainerPorts") ?: "false").toBoolean() + private val USE_FIXED_PORTS = System.getProperty("useFixedContainerPorts")?.toBoolean() ?: false } } diff --git a/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt b/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt index 1e92691..87f48f5 100644 --- a/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt +++ b/src/test/kotlin/org/prebid/cache/functional/util/PrebidCacheUtil.kt @@ -1,11 +1,10 @@ package org.prebid.cache.functional.util -import java.util.UUID +import java.util.* fun getRandomUuid(): String = UUID.randomUUID().toString() fun getRandomString(length: Int = 16): String { val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') - return (1..length).map { allowedChars.random() } - .joinToString("") + return List(length) { allowedChars.random() }.joinToString("") } From e0b1febbca7069a96a7fe7545de3db8238e14ef9 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Mon, 23 May 2022 15:19:28 +0300 Subject: [PATCH 10/11] Make flucky unit test more sustainable (#69) --- .../cache/handlers/PostCacheHandlerTests.java | 13 +++++----- .../org/prebid/cache/util/AwaitilityUtil.java | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/prebid/cache/util/AwaitilityUtil.java diff --git a/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java b/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java index c9e5ba4..2419055 100644 --- a/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java +++ b/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java @@ -36,14 +36,13 @@ import java.util.Collections; import java.util.Date; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Supplier; import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.given; +import static org.prebid.cache.util.AwaitilityUtil.awaitAndVerify; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; @ExtendWith(SpringExtension.class) @@ -154,7 +153,7 @@ void testSecondaryCacheSuccess() { final var responseMono = handler.save(requestMono); - Consumer consumer = + final Consumer consumer = serverResponse -> assertEquals(200, serverResponse.statusCode().value()); StepVerifier.create(responseMono) @@ -162,11 +161,11 @@ void testSecondaryCacheSuccess() { .expectComplete() .verify(); - await().atLeast(10, TimeUnit.MILLISECONDS); - - verify(postRequestedFor(urlPathEqualTo("/cache")) + final var requestPatternBuilder = postRequestedFor(urlPathEqualTo("/cache")) .withQueryParam("secondaryCache", equalTo("yes")) - .withHeader(HttpHeaders.CONTENT_TYPE, equalToIgnoreCase("application/json"))); + .withHeader(HttpHeaders.CONTENT_TYPE, equalToIgnoreCase("application/json")); + + awaitAndVerify(requestPatternBuilder, 5000); } @Test diff --git a/src/test/java/org/prebid/cache/util/AwaitilityUtil.java b/src/test/java/org/prebid/cache/util/AwaitilityUtil.java new file mode 100644 index 0000000..c28264f --- /dev/null +++ b/src/test/java/org/prebid/cache/util/AwaitilityUtil.java @@ -0,0 +1,24 @@ +package org.prebid.cache.util; + +import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; + +import java.util.concurrent.TimeUnit; + +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.awaitility.Awaitility.await; + +public class AwaitilityUtil { + + public static void awaitAndVerify(RequestPatternBuilder requestPatternBuilder, long timeoutMillis) { + awaitAndVerify(1, requestPatternBuilder, timeoutMillis); + } + + public static void awaitAndVerify(int count, RequestPatternBuilder requestPatternBuilder, long timeoutMillis) { + await() + .atMost(timeoutMillis, TimeUnit.MILLISECONDS) + .until(() -> findAll(requestPatternBuilder).size() >= count); + + verify(requestPatternBuilder); + } +} From ee71b940318a7ea708dc915231cac3fa16260a25 Mon Sep 17 00:00:00 2001 From: mhupalo Date: Mon, 23 May 2022 17:06:08 +0300 Subject: [PATCH 11/11] Loose cache timeout ms --- .../functional/testcontainers/PrebidCacheContainerConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 06e9fdb..cfe7299 100644 --- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt +++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt @@ -36,7 +36,7 @@ class PrebidCacheContainerConfig(private val redisHost: String, private val aero mapOf( "cache.min.expiry" to minExpiry, "cache.max.expiry" to maxExpiry, - "cache.expiry.sec" to "300" + "cache.expiry.sec" to "500" ) fun getCacheTimeoutConfig(timeoutMs: String): Map =