Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation for the test framework #1755

Merged
merged 4 commits into from
Mar 30, 2022
Merged
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
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/gdpr.md)

## Maintenance
Expand Down
115 changes: 115 additions & 0 deletions docs/developers/functional-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
## 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.
[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.
Net-burst marked this conversation as resolved.
Show resolved Hide resolved

## Getting Started

- Install [Docker](https://docs.docker.com/engine/install/)
- 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 dev 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 to not launch containers for unit tests.
`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 container `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, COALESCE(storedAuctionResponse, storedBidResponse) as 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