Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
12 changes: 4 additions & 8 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,9 +345,11 @@
<artifactId>influxdb</artifactId>
<scope>test</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.wiremock/wiremock -->
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>3.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -623,7 +620,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
@@ -0,0 +1,18 @@
package org.prebid.server.functional.model

enum HttpStatusCode {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use org.apache.http.HttpStatus instead of our own codes


PROCESSING_102(102),
OK_200(200),
NO_CONTENT_204(204),
BAD_REQUEST_400(400),
NOT_FOUNT_404(404),
INTERNAL_SERVER_ERROR_500(500),
SERVICE_UNAVAILABLE_503(503)

Integer code

HttpStatusCode(Integer code){
this.code = code
}
}
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-latest"))
.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.prebid.server.functional.model.HttpStatusCode.OK_200

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(urlMatching("^$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(urlMatching("^$endpoint(\\?.*)?\$"))
.withRequestBody(matchingJsonPath("\$[?(@." + requestMatchPath + " == '" + bidRequestId + "')]"))
.build()
Comment on lines +61 to +64
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be simplified:

RequestPattern getRequest(String bidRequestId, String requestMatchPath) {
    postRequestedFor(urlMatching("^${endpoint}(\\?.*)?\$"))
        .withRequestBody(matchingJsonPath("\$[?(@.${requestMatchPath} == '${bidRequestId}')]"))
        .build()
}

}

@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()
.withTransformers("response-template")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can add global-response-templating and remove this from all responses https://wiremock.org/docs/standalone/docker/#building-your-own-image

.withStatus(OK_200.code)
.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,37 @@
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()))
}
void setResponse() {}

@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) {
return null
}
}
Loading
Loading