Skip to content

Commit

Permalink
Add documentation for the test framework
Browse files Browse the repository at this point in the history
  • Loading branch information
mtuchkova committed Feb 22, 2022
1 parent bf4b498 commit 269e1bb
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 134 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ and verify response status is `200 OK`.
- [Cookie Syncs](https://docs.prebid.org/prebid-server/developers/pbs-cookie-sync.html)
- [Stored Requests](docs/developers/stored-requests.md)
- [Unit Tests](docs/developers/unit-tests.md)
- [Functional Tests](docs/developers/functional-tests.md)
- [GDPR](docs/developers/gdpr.md)

## Maintenance
Expand Down
114 changes: 114 additions & 0 deletions docs/developers/functional-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
## Functional tests

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.
And [MockServer](https://www.mock-server.com/) for mocking external services.

## Getting Started

- Install [Docker](https://docs.docker.com/engine/install/ubuntu/)
- Set launchContainers system property to 'true'

### Prepare PBS image

`mvn clean -B package -DskipUnitTests=true docker:build` - to build image

> Don't forget to rebuild image for manual test start (after change in code)
### Run integration tests

- You can use `mvn verify` - to include all previous steps (including java test) because groovy runs
in `failsafe:integration-test` phase
- Or use more granular `mvn -B verify -DskipUnitTests=true` for functional tests only

## Developing

Functional tests need to have name template **.\*Spec.groovy**

- `/functional/*Spec` - contain all test suites.
- `/functional/util` - different useful methods and wrappers.
- `/functional/repository` - interaction with DB. Notice, that some related objects need to use ID generated by DB.
Primary Key will be inserted after saving instance into DB.
- `/functional/service/PrebidServerService` - responsible for all PBS http calls.
- `/functional/testcontainers/Dependencies` - stores dependencies and manages mySql and NetworkServiceContainer containers.
- `/functional/testcontainers/ErrorListener` - logs request and response in case of falling test.
- `/functional/testcontainers/PbsConfig` - collects PBS properties.
- `/functional/testcontainers/PbsServiceFactory` - manage PBS containers according to container limit.
- `/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.


**Properties:**

`launchContainers` - responsible for starting the MockServer and the MySQLContainer container. Default value is false.
`max.containers.count` - maximum number of simultaneously running PBS containers. Default value is 2.
`skipFunctionalTests` - allow to skip funtional tests. Default value is false.
`skipUnitTests` - allow to skip unit tests. Default value is false.

**Debug:**

An application running inside a Docker container is treated as a remote application, so you can attach the debugger to it.

In order to obtain the debug port you need to use `getMappedPort(8000)` based on the desired container.

### Containers

Every container expose random port which you can see `docker ps` - running containers. (add flag `-a` to see exited containers)

You can observe logs inside contUsernameainer `docker logs <container_id>`. (You don't need to write all id, just use 2-3 symbols)

#### NetworkServiceContainer

Container for mocking different calls from PBS: prebid cache, bidders, currency conversion service, PubStack analytics, http data source

`/auction` - default mocked bidder call returns response based on information from request:

```
{
"id":"0a00b9a1-06c0-4001-9d64-b6a3f7d995e6",
"seatbid":[
{
"bid":[
{
"id":"c550cf25-3a09-480b-852f-e75a009bbefd",
"impid":"b9a2756a-6aef-438c-85ed-53331a7b4c53",
"price":1.23,
"crid":"1"
}
]
}
]
}
```

`/cache` - mocked PBC call returns:

```
{
"responses":[
{
"uuid":"8565b07a-8a64-476b-ac68-cc09b4560625"
}
]
}
```

#### MySQLContainer

Container for Mysql database.

- Use `org/prebid/server/functional/db_schema.sql` script for scheme.
- DataBase: `prebid`
- Username: `prebid`
- Password: `prebid`

#### PrebidServerContainer

Starting image `prebid/prebid-server:latest`.
Waits for container is ready by checking "/status" endpoint.

#### TestContainer library

TestContainer will start special `Ryuk container` which will manage a lifecycle of all depending containers.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package org.prebid.server.functional.testcontainers

import org.prebid.server.functional.testcontainers.container.NetworkServiceContainer
import org.prebid.server.functional.util.ObjectMapperWrapper
import org.prebid.server.functional.util.PBSUtils
import org.prebid.server.functional.util.SystemProperties
import org.testcontainers.containers.MySQLContainer
import org.testcontainers.containers.Network
import org.testcontainers.lifecycle.Startables

import static org.prebid.server.functional.util.SystemProperties.MOCKSERVER_VERSION

class Dependencies {

private static final Boolean IS_LAUNCH_CONTAINERS = Boolean.valueOf(
PBSUtils.getPropertyOrDefault("launchContainers", "false"))
SystemProperties.getPropertyOrDefault("launchContainers", "false"))

static final ObjectMapperWrapper objectMapperWrapper = new ObjectMapperWrapper()

Expand All @@ -23,7 +25,7 @@ class Dependencies {
.withInitScript("org/prebid/server/functional/db_schema.sql")
.withNetwork(network)

static final NetworkServiceContainer networkServiceContainer = new NetworkServiceContainer(System.getProperty("mockserver.version"))
static final NetworkServiceContainer networkServiceContainer = new NetworkServiceContainer(MOCKSERVER_VERSION)
.withNetwork(network)

static void start() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package org.prebid.server.functional.testcontainers

import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
import org.spockframework.runtime.extension.IAnnotationDrivenExtension
import org.spockframework.runtime.model.SpecInfo

class PBSTestExtension extends AbstractAnnotationDrivenExtension<PBSTest> {
class PBSTestExtension implements IAnnotationDrivenExtension<PBSTest> {

@Override
void visitSpecAnnotation(PBSTest annotation, SpecInfo spec) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.prebid.server.functional.testcontainers

import org.testcontainers.containers.MySQLContainer

import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer
import static org.prebid.server.functional.testcontainers.container.PrebidServerContainer.ADMIN_ENDPOINT_PASSWORD
import static org.prebid.server.functional.testcontainers.container.PrebidServerContainer.ADMIN_ENDPOINT_USERNAME

final class PbsConfig {

private static final String DB_ACCOUNT_QUERY = """
SELECT JSON_MERGE_PATCH(JSON_OBJECT('id', uuid,
'status', status,
'auction', JSON_OBJECT('price-granularity', price_granularity,
'banner-cache-ttl', banner_cache_ttl,
'video-cache-ttl', video_cache_ttl,
'truncate-target-attr', truncate_target_attr,
'default-integration', default_integration,
'bid-validations', bid_validations,
'events', JSON_OBJECT('enabled', NOT NOT (events_enabled))),
'privacy', JSON_OBJECT('gdpr', tcf_config),
'analytics', analytics_config),
COALESCE(config, '{}')) as consolidated_config
FROM accounts_account
WHERE uuid = %ACCOUNT_ID%
LIMIT 1
"""

static final Map<String, String> DEFAULT_ENV = [
"auction.ad-server-currency" : "USD",
"auction.stored-requests-timeout-ms" : "1000",
"metrics.prefix" : "prebid",
"status-response" : "ok",
"gdpr.default-value" : "0",
"settings.database.account-query" : DB_ACCOUNT_QUERY,
"settings.database.stored-requests-query" : "SELECT accountId, reqid, requestData, 'request' as dataType FROM stored_requests WHERE reqid IN (%REQUEST_ID_LIST%) UNION ALL SELECT accountId, reqid, requestData, 'imp' as dataType FROM stored_requests WHERE reqid IN (%IMP_ID_LIST%)",
"settings.database.amp-stored-requests-query": "SELECT accountId, reqid, requestData, 'request' as dataType FROM stored_requests WHERE reqid IN (%REQUEST_ID_LIST%)",
"settings.database.stored-responses-query" : "SELECT resid, responseData FROM stored_responses WHERE resid IN (%RESPONSE_ID_LIST%)"
].asImmutable()

static Map<String, String> getPubstackAnalyticsConfig(String scopeId) {
["analytics.pubstack.enabled" : "true",
"analytics.pubstack.endpoint" : networkServiceContainer.rootUri,
"analytics.pubstack.scopeid" : scopeId,
"analytics.pubstack.configuration-refresh-delay-ms": "1000",
"analytics.pubstack.buffers.size-bytes" : "1",
"analytics.pubstack.timeout-ms" : "100"].asImmutable()
}

static Map<String, String> getHttpSettingsConfig(String rootUri = networkServiceContainer.rootUri) {
["settings.http.endpoint" : "$rootUri/stored-requests".toString(),
"settings.http.amp-endpoint" : "$rootUri/amp-stored-requests".toString(),
"settings.http.video-endpoint" : "$rootUri/video-stored-requests".toString(),
"settings.http.category-endpoint": "$rootUri/video-categories".toString()].asImmutable()
}

static Map<String, String> getAdminEndpointConfig() {
["admin-endpoints.currency-rates.enabled" : "true",
"currency-converter.external-rates.enabled" : "true",
("admin-endpoints.credentials.$ADMIN_ENDPOINT_USERNAME".toString()): ADMIN_ENDPOINT_PASSWORD,
"admin-endpoints.logging-httpinteraction.enabled" : "true"
].asImmutable()
}

static Map<String, String> getDefaultBiddersConfig() {
["adapter-defaults.enabled" : "false",
"adapter-defaults.modifying-vast-xml-allowed": "true",
"adapter-defaults.pbs-enforces-ccpa" : "true"
].asImmutable()
}

static Map<String, String> getBidderConfig(String rootUri = networkServiceContainer.rootUri) {
["adapters.generic.enabled" : "true",
"adapters.generic.endpoint" : "$rootUri/auction".toString(),
"adapters.generic.usersync.url" : "$rootUri/generic-usersync".toString(),
"adapters.generic.usersync.type": "redirect"
]
}

static Map<String, String> getPrebidCacheConfig(String host = networkServiceContainer.hostAndPort) {
["cache.scheme": "http",
"cache.host" : "$host".toString(),
"cache.path" : "/cache",
"cache.query" : "uuid="
].asImmutable()
}

static Map<String, String> getMySqlConfig(MySQLContainer mysql = Dependencies.mysqlContainer) {
["settings.database.type" : "mysql",
"settings.database.host" : mysql.getNetworkAliases().get(0),
"settings.database.port" : mysql.exposedPorts.get(0) as String,
"settings.database.dbname" : mysql.databaseName,
"settings.database.user" : mysql.username,
"settings.database.password" : mysql.password,
"settings.database.pool-size": "2" // setting 2 here to leave some slack for the PBS
].asImmutable()
}

static Map<String, String> getMetricConfig() {
["admin-endpoints.collected-metrics.enabled": "true"].asImmutable()
}

private PbsConfig() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import org.prebid.server.functional.service.PrebidServerService
import org.prebid.server.functional.testcontainers.container.NetworkServiceContainer
import org.prebid.server.functional.testcontainers.container.PrebidServerContainer
import org.prebid.server.functional.util.ObjectMapperWrapper
import org.prebid.server.functional.util.PBSUtils
import org.prebid.server.functional.util.SystemProperties

// TODO make container instance into a POGO and add the ability for any given container to live through stopContainers()
class PbsServiceFactory {

private static final Map<Map<String, String>, PrebidServerContainer> containers = [:]
private static final int MAX_CONTAINERS_COUNT = Integer.parseInt(
PBSUtils.getPropertyOrDefault("max.containers.count", "2"))
SystemProperties.getPropertyOrDefault("max.containers.count", "2"))

private final ObjectMapperWrapper mapper
private final NetworkServiceContainer networkServiceContainer
Expand Down Expand Up @@ -45,22 +45,6 @@ class PbsServiceFactory {
remove(containers)
}

Map<String, String> pubstackAnalyticsConfig(String scopeId) {
["analytics.pubstack.enabled" : "true",
"analytics.pubstack.endpoint" : networkServiceContainer.rootUri,
"analytics.pubstack.scopeid" : scopeId,
"analytics.pubstack.configuration-refresh-delay-ms": "1000",
"analytics.pubstack.buffers.size-bytes" : "1",
"analytics.pubstack.timeout-ms" : "100"]
}

Map<String, String> httpSettings() {
["settings.http.endpoint" : "$networkServiceContainer.rootUri/stored-requests".toString(),
"settings.http.amp-endpoint" : "$networkServiceContainer.rootUri/amp-stored-requests".toString(),
"settings.http.video-endpoint" : "$networkServiceContainer.rootUri/video-stored-requests".toString(),
"settings.http.category-endpoint": "$networkServiceContainer.rootUri/video-categories".toString()]
}

private static void remove(Map<Map<String, String>, PrebidServerContainer> map) {
map.each { key, value ->
value.stop()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.prebid.server.functional.testcontainers

import org.spockframework.runtime.extension.AbstractGlobalExtension
import org.spockframework.runtime.extension.IGlobalExtension

class TestcontainersExtension extends AbstractGlobalExtension {
class TestcontainersExtension implements IGlobalExtension {

@Override
void start() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class NetworkServiceContainer extends MockServerContainer {
}

String getRootUri() {
"http://${getHostAndPort()}"
"http://${hostAndPort}"
}

@Override
Expand Down
Loading

0 comments on commit 269e1bb

Please sign in to comment.