Skip to content
8 changes: 4 additions & 4 deletions docs/developers/functional-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Main language: Groovy. Project functional tests use [Spock](https://spockframework.org/) as a main testing framework.
Also used [Docker](https://www.docker.com/) for running PBS and other services.
[Testcontainers](https://www.testcontainers.org/) is used as provider of lightweight, throwaway instances of PBS, MySQLContainer, MockServerContainer containers.
And [MockServer](https://www.mock-server.com/) for mocking external services.
[Testcontainers](https://www.testcontainers.org/) is used as provider of lightweight, throwaway instances of PBS, MySQLContainer, WireMock containers.
And [WireMock](https://wiremock.org/) for mocking external services.

## Getting Started

Expand Down Expand Up @@ -64,12 +64,12 @@ Functional tests need to have name template **.\*Spec.groovy**
- `/functional/testcontainers/PBSTestExtension` - allows to hook into a spec’s lifecycle to add ErrorListener using annotation `PBSTest`.
- `/functional/testcontainers/TestcontainersExtension` - allow to hook into a spec’s lifecycle to start and stop support service containers using global extension.
- `/functional/testcontainers/container` - responsible for creating and configuring containers.
- `/functional/testcontainers/scaffolding/NetworkScaffolding` - makes HTTP requests to a MockServer.
- `/functional/testcontainers/scaffolding/NetworkScaffolding` - makes HTTP requests to a WireMock.


**Properties:**

`launchContainers` - responsible for starting the MockServer and the MySQLContainer container. Default value is false to not launch containers for unit tests.
`launchContainers` - responsible for starting containers. Default value is false to not launch containers for unit tests.
`tests.max-container-count` - maximum number of simultaneously running PBS containers. Default value is 5.
`skipFunctionalTests` - allow to skip funtional tests. Default value is false.
`skipUnitTests` - allow to skip unit tests. Default value is false.
Expand Down
17 changes: 0 additions & 17 deletions extra/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@
<!-- Project test dependency versions -->
<wiremock.version>3.12.1</wiremock.version>
<spock.version>2.4-M6-groovy-4.0</spock.version>
<!--TODO: replace with WireMock -->
<mockserver.version>5.15.0</mockserver.version>

<!-- Test properties -->
<skipUnitTests>false</skipUnitTests>
Expand Down Expand Up @@ -262,21 +260,6 @@
<artifactId>json-logic-java</artifactId>
<version>${json-logic.version}</version>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
<version>${mockserver.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
11 changes: 0 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,6 @@
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mockserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
Expand All @@ -350,11 +345,6 @@
<artifactId>influxdb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.influxdb</groupId>
<artifactId>influxdb-java</artifactId>
Expand Down Expand Up @@ -623,7 +613,6 @@
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<mockserver.version>${mockserver.version}</mockserver.version>
<pbs.version>${project.version}</pbs.version>
<tests.max-container-count>5</tests.max-container-count>
<tests.fixed-container-ports>false</tests.fixed-container-ports>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.prebid.server.functional.model

/**
* This marker interface should limit the possible values used by the MockServerClientWrapper.
* This marker interface should limit the possible values used by the WireMockClientWrapper.
*/
interface ResponseModel {}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.lifecycle.Startables
import org.testcontainers.utility.DockerImageName

import static org.prebid.server.functional.util.SystemProperties.MOCKSERVER_VERSION
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3

class Dependencies {
Expand Down Expand Up @@ -42,7 +41,7 @@ class Dependencies {
.withDatabase("prebid")
.withNetwork(network)

static final NetworkServiceContainer networkServiceContainer = new NetworkServiceContainer(MOCKSERVER_VERSION)
static final NetworkServiceContainer networkServiceContainer = new NetworkServiceContainer()
.withNetwork(network)

static LocalStackContainer localStackContainer
Expand All @@ -52,13 +51,15 @@ class Dependencies {
localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack:s3-community-archive"))
.withNetwork(network)
.withServices(S3)
Startables.deepStart([networkServiceContainer, mysqlContainer, localStackContainer, influxdbContainer]).join()
Startables.deepStart([networkServiceContainer, mysqlContainer, localStackContainer,
influxdbContainer]).join()
}
}

static void stop() {
if (IS_LAUNCH_CONTAINERS) {
[networkServiceContainer, mysqlContainer, localStackContainer, influxdbContainer].parallelStream()
[networkServiceContainer, mysqlContainer, localStackContainer, influxdbContainer]
.parallelStream()
.forEach({ it.stop() })
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package org.prebid.server.functional.testcontainers.container

import org.testcontainers.containers.MockServerContainer
import org.testcontainers.containers.GenericContainer
import org.testcontainers.containers.Network
import org.testcontainers.utility.DockerImageName

class NetworkServiceContainer extends MockServerContainer {
class NetworkServiceContainer extends GenericContainer<NetworkServiceContainer> {

NetworkServiceContainer(String version) {
super(DockerImageName.parse("mockserver/mockserver:mockserver-$version"))
NetworkServiceContainer() {
super(DockerImageName.parse("wiremock/wiremock:3.3.1"))
def aliasWithTopLevelDomain = "${getNetworkAliases().first()}.com".toString()
withCreateContainerCmdModifier { it.withHostName(aliasWithTopLevelDomain) }
setNetworkAliases([aliasWithTopLevelDomain])
withCommand("--disable-gzip")
withExposedPorts(8080)
}

String getHostAndPort() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,77 @@
package org.prebid.server.functional.testcontainers.scaffolding

import org.mockserver.matchers.TimeToLive
import org.mockserver.matchers.Times
import org.mockserver.model.HttpRequest
import org.mockserver.model.HttpResponse
import com.github.tomakehurst.wiremock.matching.RequestPattern
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder
import org.prebid.server.functional.model.bidderspecific.BidderRequest
import org.prebid.server.functional.model.request.auction.Banner
import org.prebid.server.functional.model.request.auction.BidRequest
import org.prebid.server.functional.model.request.auction.Format
import org.prebid.server.functional.model.request.auction.Imp
import org.prebid.server.functional.model.response.auction.BidResponse
import org.testcontainers.containers.MockServerContainer
import org.prebid.server.functional.testcontainers.container.NetworkServiceContainer

import static org.mockserver.model.HttpRequest.request
import static org.mockserver.model.HttpResponse.response
import static org.mockserver.model.HttpStatusCode.OK_200
import static org.mockserver.model.JsonPathBody.jsonPath
import static com.github.tomakehurst.wiremock.client.WireMock.post
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor
import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo
import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse
import static org.apache.http.HttpStatus.SC_OK

class Bidder extends NetworkScaffolding {

Bidder(MockServerContainer mockServerContainer, String endpoint = "/auction") {
super(mockServerContainer, endpoint)
private static final String DEFAULT_BODY_RESPONSE =
'''
{
"id": "{{jsonPath request.body '$.id'}}",
"seatbid": [
{
"bid": [
{{#each (jsonPath request.body '$.imp')}}
{
"id": "bid-{{randomInt}}",
"impid": "{{this.id}}",
"price": 10.0,
{{#if this.banner}}
"w": {{this.banner.format.[0].w}},
"h": {{this.banner.format.[0].h}},
{{/if}}
"crid": "creative-{{@index}}"
}{{#unless @last}},{{/unless}}
{{/each}}
],
"seat": "generic"
}
]
}
'''

Bidder(NetworkServiceContainer wireMockContainer, String endpoint = "/auction") {
super(wireMockContainer, endpoint)
}

@Override
protected HttpRequest getRequest(String bidRequestId) {
request().withPath(endpoint)
.withBody(jsonPath("\$[?(@.id == '$bidRequestId')]"))
protected RequestPattern getRequest() {
postRequestedFor(urlEqualTo(endpoint))
.build()
}

@Override
protected HttpRequest getRequest() {
request().withPath(endpoint)
protected RequestPatternBuilder getRequest(String bidRequestId) {
postRequestedFor(urlPathEqualTo(endpoint))
.withRequestBody(matchingJsonPath("\$.id", equalTo(bidRequestId)))
}

HttpRequest getRequest(String bidRequestId, String requestMatchPath) {
request().withPath(endpoint)
.withBody(jsonPath("\$[?(@.$requestMatchPath == '$bidRequestId')]"))
RequestPattern getRequest(String bidRequestId, String requestMatchPath) {
postRequestedFor(urlPathEqualTo(endpoint))
.withRequestBody(matchingJsonPath("\$[?(@.${requestMatchPath} == '${bidRequestId}')]"))
.build()
Comment thread
osulzhenko marked this conversation as resolved.
}

@Override
void setResponse() {
mockServerClient.when(request().withPath(endpoint), Times.unlimited(), TimeToLive.unlimited(), -10)
.respond {request -> request.withPath(endpoint)
? response().withStatusCode(OK_200.code()).withBody(getBodyByRequest(request))
: HttpResponse.notFoundResponse()}
wireMockClient.register(post(urlPathEqualTo(endpoint))
.atPriority(Integer.MAX_VALUE)
.willReturn(aResponse()
.withStatus(SC_OK)
.withTransformers("response-template")
Comment thread
osulzhenko marked this conversation as resolved.
.withBody(DEFAULT_BODY_RESPONSE)))
}

List<BidderRequest> getBidderRequests(String bidRequestId) {
Expand All @@ -65,20 +92,4 @@ class Bidder extends NetworkScaffolding {
Map<String, List<String>> getLastRecordedBidderRequestHeaders(String bidRequestId) {
return getLastRecordedRequestHeaders(bidRequestId)
}

private String getBodyByRequest(HttpRequest request) {
def requestString = request.bodyAsString
def jsonNode = toJsonNode(requestString)
def id = jsonNode.get("id").asText()
def impNode = jsonNode.get("imp")
def imps = impNode.collect {
def formatNode = it.get("banner") != null ? it.get("banner").get("format") : null
new Imp(id: it.get("id").asText(),
banner: formatNode != null
? new Banner(format: [new Format(width: formatNode.first().get("w").asInt(), height: formatNode.first().get("h").asInt())])
: null)}
def bidRequest = new BidRequest(id: id, imp: imps)
def response = BidResponse.getDefaultBidResponse(bidRequest)
encode(response)
}
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,39 @@
package org.prebid.server.functional.testcontainers.scaffolding

import org.mockserver.model.HttpRequest
import com.github.tomakehurst.wiremock.matching.RequestPattern
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder
import org.prebid.server.functional.model.mock.services.currencyconversion.CurrencyConversionRatesResponse
import org.testcontainers.containers.MockServerContainer
import org.prebid.server.functional.testcontainers.container.NetworkServiceContainer

import static org.mockserver.model.HttpRequest.request
import static org.mockserver.model.HttpResponse.response
import static org.mockserver.model.HttpStatusCode.OK_200
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo
import static org.prebid.server.functional.util.CurrencyUtil.DEFAULT_CURRENCY_RATES

class CurrencyConversion extends NetworkScaffolding {

static final String CURRENCY_ENDPOINT_PATH = "/currency"
private static final CurrencyConversionRatesResponse DEFAULT_RATES_RESPONSE = CurrencyConversionRatesResponse.getDefaultCurrencyConversionRatesResponse(DEFAULT_CURRENCY_RATES)

CurrencyConversion(MockServerContainer mockServerContainer) {
super(mockServerContainer, CURRENCY_ENDPOINT_PATH)
CurrencyConversion(NetworkServiceContainer wireMockContainer) {
super(wireMockContainer, CURRENCY_ENDPOINT_PATH)
}

void setCurrencyConversionRatesResponse(CurrencyConversionRatesResponse conversionRatesResponse = DEFAULT_RATES_RESPONSE) {
setResponse(request, conversionRatesResponse)
setResponse(getRequest(), conversionRatesResponse)
}

@Override
void setResponse() {
mockServerClient.when(request().withPath(endpoint))
.respond(response().withStatusCode(OK_200.code()))
throw new UnsupportedOperationException()
}

@Override
protected HttpRequest getRequest(String ignored) {
request().withMethod("GET")
.withPath(CURRENCY_ENDPOINT_PATH)
protected RequestPattern getRequest() {
getRequestedFor(urlEqualTo(CURRENCY_ENDPOINT_PATH)).build()
}

@Override
protected HttpRequest getRequest() {
request().withMethod("GET")
.withPath(CURRENCY_ENDPOINT_PATH)
protected RequestPatternBuilder getRequest(String value) {
throw new UnsupportedOperationException()
}
}
Loading
Loading