- * Expected the endpoint to satisfy the following API: + * Expected the endpoint to satisfy the following API (URL is encoded): *
* GET {endpoint}?request-ids=["req1","req2"]&imp-ids=["imp1","imp2","imp3"] *
+ * or settings.http.rfc3986-compatible is set to true + *
+ * * GET {endpoint}?request-id=req1&request-id=req2&imp-id=imp1&imp-id=imp2&imp-id=imp3 + * *
* This endpoint should return a payload like: *
* {
@@ -76,20 +82,27 @@ public class HttpApplicationSettings implements ApplicationSettings {
private final String categoryEndpoint;
private final HttpClient httpClient;
private final JacksonMapper mapper;
+ private final boolean isRfc3986Compatible;
+
+ public HttpApplicationSettings(HttpClient httpClient,
+ JacksonMapper mapper,
+ String endpoint,
+ String ampEndpoint,
+ String videoEndpoint,
+ String categoryEndpoint,
+ boolean isRfc3986Compatible) {
- public HttpApplicationSettings(HttpClient httpClient, JacksonMapper mapper, String endpoint, String ampEndpoint,
- String videoEndpoint, String categoryEndpoint) {
this.httpClient = Objects.requireNonNull(httpClient);
this.mapper = Objects.requireNonNull(mapper);
- this.endpoint = HttpUtil.validateUrl(Objects.requireNonNull(endpoint));
- this.ampEndpoint = HttpUtil.validateUrl(Objects.requireNonNull(ampEndpoint));
- this.videoEndpoint = HttpUtil.validateUrl(Objects.requireNonNull(videoEndpoint));
- this.categoryEndpoint = HttpUtil.validateUrl(Objects.requireNonNull(categoryEndpoint));
+ this.endpoint = HttpUtil.validateUrlSyntax(Objects.requireNonNull(endpoint));
+ this.ampEndpoint = HttpUtil.validateUrlSyntax(Objects.requireNonNull(ampEndpoint));
+ this.videoEndpoint = HttpUtil.validateUrlSyntax(Objects.requireNonNull(videoEndpoint));
+ this.categoryEndpoint = HttpUtil.validateUrlSyntax(Objects.requireNonNull(categoryEndpoint));
+ this.isRfc3986Compatible = isRfc3986Compatible;
}
@Override
public Future getAccountById(String accountId, Timeout timeout) {
-
return fetchAccountsByIds(Collections.singleton(accountId), timeout)
.map(accounts -> accounts.stream()
.findFirst()
@@ -111,15 +124,20 @@ private Future> fetchAccountsByIds(Set accountIds, Timeout
.recover(Future::failedFuture);
}
- private static String accountsRequestUrlFrom(String endpoint, Set accountIds) {
- final StringBuilder url = new StringBuilder(endpoint);
- url.append(endpoint.contains("?") ? "&" : "?");
-
- if (!accountIds.isEmpty()) {
- url.append("account-ids=[\"").append(joinIds(accountIds)).append("\"]");
+ private String accountsRequestUrlFrom(String endpoint, Set accountIds) {
+ try {
+ final URIBuilder uriBuilder = new URIBuilder(endpoint);
+ if (!accountIds.isEmpty()) {
+ if (isRfc3986Compatible) {
+ accountIds.forEach(accountId -> uriBuilder.addParameter("account-id", accountId));
+ } else {
+ uriBuilder.addParameter("account-ids", "[\"%s\"]".formatted(joinIds(accountIds)));
+ }
+ }
+ return uriBuilder.build().toString();
+ } catch (URISyntaxException e) {
+ throw new PreBidException("URL %s has bad syntax".formatted(endpoint));
}
-
- return url.toString();
}
private Future> processAccountsResponse(HttpClientResponse response, Set accountIds) {
@@ -165,9 +183,6 @@ public Future getAmpStoredData(String accountId, Set r
return fetchStoredData(ampEndpoint, requestIds, Collections.emptySet(), timeout);
}
- /**
- * Not supported and returns failed result.
- */
@Override
public Future getVideoStoredData(String accountId, Set requestIds, Set impIds,
Timeout timeout) {
@@ -240,22 +255,27 @@ private Future fetchStoredData(String endpoint, Set re
.recover(exception -> failStoredDataResponse(exception, requestIds, impIds));
}
- private static String storeRequestUrlFrom(String endpoint, Set requestIds, Set impIds) {
- final StringBuilder url = new StringBuilder(endpoint);
- url.append(endpoint.contains("?") ? "&" : "?");
-
- if (!requestIds.isEmpty()) {
- url.append("request-ids=[\"").append(joinIds(requestIds)).append("\"]");
- }
-
- if (!impIds.isEmpty()) {
+ private String storeRequestUrlFrom(String endpoint, Set requestIds, Set impIds) {
+ try {
+ final URIBuilder uriBuilder = new URIBuilder(endpoint);
if (!requestIds.isEmpty()) {
- url.append("&");
+ if (isRfc3986Compatible) {
+ requestIds.forEach(requestId -> uriBuilder.addParameter("request-id", requestId));
+ } else {
+ uriBuilder.addParameter("request-ids", "[\"%s\"]".formatted(joinIds(requestIds)));
+ }
+ }
+ if (!impIds.isEmpty()) {
+ if (isRfc3986Compatible) {
+ impIds.forEach(impId -> uriBuilder.addParameter("imp-id", impId));
+ } else {
+ uriBuilder.addParameter("imp-ids", "[\"%s\"]".formatted(joinIds(impIds)));
+ }
}
- url.append("imp-ids=[\"").append(joinIds(impIds)).append("\"]");
+ return uriBuilder.build().toString();
+ } catch (URISyntaxException e) {
+ throw new PreBidException("URL %s has bad syntax".formatted(endpoint));
}
-
- return url.toString();
}
private static String joinIds(Set ids) {
diff --git a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java
index 4e883ba2495..3f674ae814d 100644
--- a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java
+++ b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java
@@ -122,10 +122,17 @@ HttpApplicationSettings httpApplicationSettings(
@Value("${settings.http.endpoint}") String endpoint,
@Value("${settings.http.amp-endpoint}") String ampEndpoint,
@Value("${settings.http.video-endpoint}") String videoEndpoint,
- @Value("${settings.http.category-endpoint}") String categoryEndpoint) {
+ @Value("${settings.http.category-endpoint}") String categoryEndpoint,
+ @Value("${settings.http.rfc3986-compatible:false}") boolean isRfc3986Compatible) {
- return new HttpApplicationSettings(httpClient, mapper, endpoint, ampEndpoint, videoEndpoint,
- categoryEndpoint);
+ return new HttpApplicationSettings(
+ httpClient,
+ mapper,
+ endpoint,
+ ampEndpoint,
+ videoEndpoint,
+ categoryEndpoint,
+ isRfc3986Compatible);
}
}
diff --git a/src/main/java/org/prebid/server/spring/config/bidder/MadsenseConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/MadsenseConfiguration.java
new file mode 100644
index 00000000000..a1cc34057e3
--- /dev/null
+++ b/src/main/java/org/prebid/server/spring/config/bidder/MadsenseConfiguration.java
@@ -0,0 +1,41 @@
+package org.prebid.server.spring.config.bidder;
+
+import org.prebid.server.bidder.BidderDeps;
+import org.prebid.server.bidder.madsense.MadsenseBidder;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
+import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
+import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
+import org.prebid.server.spring.env.YamlPropertySourceFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+import jakarta.validation.constraints.NotBlank;
+
+@Configuration
+@PropertySource(value = "classpath:/bidder-config/madsense.yaml", factory = YamlPropertySourceFactory.class)
+public class MadsenseConfiguration {
+
+ private static final String BIDDER_NAME = "madsense";
+
+ @Bean("madsenseConfigurationProperties")
+ @ConfigurationProperties("adapters.madsense")
+ BidderConfigurationProperties configurationProperties() {
+ return new BidderConfigurationProperties();
+ }
+
+ @Bean
+ BidderDeps madsenseBidderDeps(BidderConfigurationProperties madsenseConfigurationProperties,
+ @NotBlank @Value("${external-url}") String externalUrl,
+ JacksonMapper mapper) {
+
+ return BidderDepsAssembler.forBidder(BIDDER_NAME)
+ .withConfig(madsenseConfigurationProperties)
+ .usersyncerCreator(UsersyncerCreator.create(externalUrl))
+ .bidderCreator(config -> new MadsenseBidder(config.getEndpoint(), mapper))
+ .assemble();
+ }
+}
diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ZetaGlobalSspConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ZetaGlobalSspConfiguration.java
new file mode 100644
index 00000000000..aa98e645aa0
--- /dev/null
+++ b/src/main/java/org/prebid/server/spring/config/bidder/ZetaGlobalSspConfiguration.java
@@ -0,0 +1,43 @@
+package org.prebid.server.spring.config.bidder;
+
+import org.prebid.server.bidder.BidderDeps;
+import org.prebid.server.bidder.zeta_global_ssp.ZetaGlobalSspBidder;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
+import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
+import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
+import org.prebid.server.spring.env.YamlPropertySourceFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+import jakarta.validation.constraints.NotBlank;
+
+@Configuration
+@PropertySource(value = "classpath:/bidder-config/zeta_global_ssp.yaml", factory = YamlPropertySourceFactory.class)
+public class ZetaGlobalSspConfiguration {
+
+ private static final String BIDDER_NAME = "zeta_global_ssp";
+
+ @Bean("zetaglobalsspConfigurationProperties")
+ @ConfigurationProperties("adapters.zetaglobalssp")
+ BidderConfigurationProperties configurationProperties() {
+ return new BidderConfigurationProperties();
+ }
+
+ @Bean
+ BidderDeps zetaGlobalSspBidderDeps(@Qualifier("zetaglobalsspConfigurationProperties")
+ BidderConfigurationProperties zetaGlobalSspConfigurationProperties,
+ @NotBlank @Value("${external-url}") String externalUrl,
+ JacksonMapper mapper) {
+
+ return BidderDepsAssembler.forBidder(BIDDER_NAME)
+ .withConfig(zetaGlobalSspConfigurationProperties)
+ .usersyncerCreator(UsersyncerCreator.create(externalUrl))
+ .bidderCreator(config -> new ZetaGlobalSspBidder(config.getEndpoint(), mapper))
+ .assemble();
+ }
+}
diff --git a/src/main/java/org/prebid/server/util/HttpUtil.java b/src/main/java/org/prebid/server/util/HttpUtil.java
index ad9dd8a9238..47a5f24eda8 100644
--- a/src/main/java/org/prebid/server/util/HttpUtil.java
+++ b/src/main/java/org/prebid/server/util/HttpUtil.java
@@ -8,6 +8,7 @@
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.validator.routines.UrlValidator;
import org.prebid.server.log.ConditionalLogger;
import org.prebid.server.log.Logger;
import org.prebid.server.log.LoggerFactory;
@@ -78,12 +79,15 @@ public final class HttpUtil {
public static final String MACROS_OPEN = "{{";
public static final String MACROS_CLOSE = "}}";
+ private static final UrlValidator URL_VALIDAROR = UrlValidator.getInstance();
+
private HttpUtil() {
}
/**
* Checks the input string for using as URL.
*/
+ @Deprecated
public static String validateUrl(String url) {
if (containsMacrosses(url)) {
return url;
@@ -96,6 +100,14 @@ public static String validateUrl(String url) {
}
}
+ public static String validateUrlSyntax(String url) {
+ if (containsMacrosses(url) || URL_VALIDAROR.isValid(url)) {
+ return url;
+ }
+
+ throw new IllegalArgumentException("URL supplied is not valid: " + url);
+ }
+
// TODO: We need our own way to work with url macrosses
private static boolean containsMacrosses(String url) {
return StringUtils.contains(url, MACROS_OPEN) && StringUtils.contains(url, MACROS_CLOSE);
diff --git a/src/main/resources/bidder-config/generic.yaml b/src/main/resources/bidder-config/generic.yaml
index 4aa01b82bbb..a6522be1122 100644
--- a/src/main/resources/bidder-config/generic.yaml
+++ b/src/main/resources/bidder-config/generic.yaml
@@ -41,27 +41,6 @@ adapters:
- video
supported-vendors:
vendor-id: 0
- zeta_global_ssp:
- enabled: false
- endpoint: https://ssp.disqus.com/bid/prebid-server?sid=GET_SID_FROM_ZETA
- endpoint-compression: gzip
- meta-info:
- maintainer-email: DL-Zeta-SSP@zetaglobal.com
- app-media-types:
- - banner
- - video
- site-media-types:
- - banner
- - video
- supported-vendors:
- vendor-id: 833
- usersync:
- enabled: true
- cookie-family-name: zeta_global_ssp
- redirect:
- url: https://ssp.disqus.com/redirectuser?sid=GET_SID_FROM_ZETA&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&r={{redirect_url}}
- uid-macro: 'BUYERUID'
- support-cors: false
blue:
enabled: false
endpoint: https://prebid-us-east-1.getblue.io/?src=prebid
diff --git a/src/main/resources/bidder-config/madsense.yaml b/src/main/resources/bidder-config/madsense.yaml
new file mode 100644
index 00000000000..29dc214b093
--- /dev/null
+++ b/src/main/resources/bidder-config/madsense.yaml
@@ -0,0 +1,13 @@
+adapters:
+ madsense:
+ endpoint: https://ads.madsense.io/pbs
+ meta-info:
+ maintainer-email: prebid@madsense.io
+ app-media-types:
+ - banner
+ - video
+ site-media-types:
+ - banner
+ - video
+ supported-vendors:
+ vendor-id: 0
diff --git a/src/main/resources/bidder-config/visx.yaml b/src/main/resources/bidder-config/visx.yaml
index 9d99f8a31f0..df8384fc6c1 100644
--- a/src/main/resources/bidder-config/visx.yaml
+++ b/src/main/resources/bidder-config/visx.yaml
@@ -1,6 +1,6 @@
adapters:
visx:
- endpoint: https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_java
+ endpoint: https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard:0.1.2
meta-info:
maintainer-email: supply.partners@yoc.com
app-media-types:
diff --git a/src/main/resources/bidder-config/zeta_global_ssp.yaml b/src/main/resources/bidder-config/zeta_global_ssp.yaml
new file mode 100644
index 00000000000..a1bd90b75f1
--- /dev/null
+++ b/src/main/resources/bidder-config/zeta_global_ssp.yaml
@@ -0,0 +1,23 @@
+adapters:
+ zeta_global_ssp:
+ endpoint: https://ssp.disqus.com/bid/prebid-server?sid={{AccountID}}
+ endpoint-compression: gzip
+ ortb-version: "2.6"
+ modifying-vast-xml-allowed: true
+ meta-info:
+ maintainer-email: DL-Zeta-SSP@zetaglobal.com
+ app-media-types:
+ - banner
+ - video
+ - audio
+ site-media-types:
+ - banner
+ - video
+ - audio
+ vendor-id: 833
+ usersync:
+ cookie-family-name: zeta_global_ssp
+ redirect:
+ url: https://ssp.disqus.com/redirectuser?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&r={{redirect_url}}
+ support-cors: false
+ uid-macro: 'BUYERUID'
diff --git a/src/main/resources/static/bidder-params/madsense.json b/src/main/resources/static/bidder-params/madsense.json
new file mode 100644
index 00000000000..f45ac81f3ed
--- /dev/null
+++ b/src/main/resources/static/bidder-params/madsense.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "madSense Adapter Params",
+ "description": "A schema which validates params accepted by the madSense adapter",
+ "type": "object",
+ "properties": {
+ "company_id": {
+ "type": "string",
+ "description": "An id used to identify madSense company",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "company_id"
+ ]
+}
diff --git a/src/main/resources/static/bidder-params/zeta_global_ssp.json b/src/main/resources/static/bidder-params/zeta_global_ssp.json
index 91ff05ed089..8a6d1d0a060 100644
--- a/src/main/resources/static/bidder-params/zeta_global_ssp.json
+++ b/src/main/resources/static/bidder-params/zeta_global_ssp.json
@@ -1,10 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Zeta Global SSP Adapter Params",
- "description": "A schema which validates params accepted by the Zeta SSP adapter",
- "type": "object",
-
- "properties": {},
+ "description": "A schema which validates params accepted by the Zeta Global SSP adapter",
- "required": []
+ "type": "object",
+ "properties": {
+ "sid": {
+ "type": "integer",
+ "description": "An ID which identifies the publisher"
+ }
+ }
}
diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy
index 052bcf2f69f..41c781f372e 100644
--- a/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy
+++ b/src/test/groovy/org/prebid/server/functional/testcontainers/PbsConfig.groovy
@@ -129,8 +129,6 @@ LIMIT 1
"adapters.generic.aliases.nativo.meta-info.site-media-types" : "",
"adapters.generic.aliases.infytv.meta-info.app-media-types" : "",
"adapters.generic.aliases.infytv.meta-info.site-media-types" : "",
- "adapters.generic.aliases.zeta-global-ssp.meta-info.app-media-types" : "",
- "adapters.generic.aliases.zeta-global-ssp.meta-info.site-media-types": "",
"adapters.generic.aliases.ccx.meta-info.app-media-types" : "",
"adapters.generic.aliases.ccx.meta-info.site-media-types" : "",
"adapters.generic.aliases.adrino.meta-info.app-media-types" : "",
diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/container/NetworkServiceContainer.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/container/NetworkServiceContainer.groovy
index 53faa7165fa..8022f2e8dcc 100644
--- a/src/test/groovy/org/prebid/server/functional/testcontainers/container/NetworkServiceContainer.groovy
+++ b/src/test/groovy/org/prebid/server/functional/testcontainers/container/NetworkServiceContainer.groovy
@@ -8,6 +8,9 @@ class NetworkServiceContainer extends MockServerContainer {
NetworkServiceContainer(String version) {
super(DockerImageName.parse("mockserver/mockserver:mockserver-$version"))
+ def aliasWithTopLevelDomain = "${getNetworkAliases().first()}.com".toString()
+ withCreateContainerCmdModifier { it.withHostName(aliasWithTopLevelDomain) }
+ setNetworkAliases([aliasWithTopLevelDomain])
}
String getHostAndPort() {
diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/HttpSettings.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/HttpSettings.groovy
index de271b4123a..5af648b2bc0 100644
--- a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/HttpSettings.groovy
+++ b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/HttpSettings.groovy
@@ -1,13 +1,21 @@
package org.prebid.server.functional.testcontainers.scaffolding
+import org.mockserver.matchers.Times
+import org.mockserver.model.Header
import org.mockserver.model.HttpRequest
+import org.mockserver.model.HttpStatusCode
+import org.prebid.server.functional.model.ResponseModel
import org.testcontainers.containers.MockServerContainer
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.MediaType.APPLICATION_JSON
class HttpSettings extends NetworkScaffolding {
private static final String ENDPOINT = "/stored-requests"
+ private static final String RFC_ENDPOINT = "/stored-requests-rfc"
private static final String AMP_ENDPOINT = "/amp-stored-requests"
HttpSettings(MockServerContainer mockServerContainer) {
@@ -27,12 +35,47 @@ class HttpSettings extends NetworkScaffolding {
@Override
void setResponse() {
+ }
+
+ protected HttpRequest getRfcRequest(String accountId) {
+ request().withPath(RFC_ENDPOINT)
+ .withQueryStringParameter("account-id", accountId)
+ }
+
+
+ void setRfcResponse(String value,
+ ResponseModel responseModel,
+ HttpStatusCode statusCode = OK_200,
+ Map headers = [:]) {
+ def responseHeaders = headers.collect { new Header(it.key, it.value) }
+ def mockResponse = encode(responseModel)
+ mockServerClient.when(getRfcRequest(value), Times.unlimited())
+ .respond(response().withStatusCode(statusCode.code())
+ .withBody(mockResponse, APPLICATION_JSON)
+ .withHeaders(responseHeaders))
+ }
+ int getRfcRequestCount(String value) {
+ mockServerClient.retrieveRecordedRequests(getRfcRequest(value))
+ .size()
}
@Override
void reset() {
super.reset(ENDPOINT)
+ super.reset(RFC_ENDPOINT)
super.reset(AMP_ENDPOINT)
}
+
+ static String getEndpoint() {
+ return ENDPOINT
+ }
+
+ static String getAmpEndpoint() {
+ return AMP_ENDPOINT
+ }
+
+ static String getRfcEndpoint() {
+ return RFC_ENDPOINT
+ }
}
diff --git a/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy
index 2c6d1556a81..01f60ac2808 100644
--- a/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy
+++ b/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy
@@ -15,7 +15,6 @@ import org.prebid.server.functional.testcontainers.PbsConfig
import org.prebid.server.functional.testcontainers.scaffolding.HttpSettings
import org.prebid.server.functional.util.PBSUtils
import org.prebid.server.util.ResourceUtil
-import spock.lang.Shared
import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer
@@ -23,11 +22,25 @@ import static org.prebid.server.functional.testcontainers.Dependencies.networkSe
class HttpSettingsSpec extends BaseSpec {
// Check that PBS actually applied account config only possible by relying on side effects.
- @Shared
- HttpSettings httpSettings = new HttpSettings(networkServiceContainer)
+ static PrebidServerService prebidServerService
+ static PrebidServerService prebidServerServiceWithRfc
- @Shared
- PrebidServerService prebidServerService = pbsServiceFactory.getService(PbsConfig.httpSettingsConfig)
+ private static final HttpSettings httpSettings = new HttpSettings(networkServiceContainer)
+ private static final Map PBS_CONFIG_WITH_RFC = new HashMap<>(PbsConfig.httpSettingsConfig) +
+ ['settings.http.endpoint': "${networkServiceContainer.rootUri}${HttpSettings.rfcEndpoint}".toString(),
+ 'settings.http.rfc3986-compatible': 'true']
+
+ def setupSpec() {
+ prebidServerService = pbsServiceFactory.getService(PbsConfig.httpSettingsConfig)
+ prebidServerServiceWithRfc = pbsServiceFactory.getService(PBS_CONFIG_WITH_RFC)
+ bidder.setResponse()
+ vendorList.setResponse()
+ }
+
+ def cleanupSpec() {
+ prebidServerService = pbsServiceFactory.removeContainer(PbsConfig.httpSettingsConfig)
+ prebidServerService = pbsServiceFactory.removeContainer(PBS_CONFIG_WITH_RFC)
+ }
def "PBS should take account information from http data source on auction request"() {
given: "Get basic BidRequest with generic bidder and set gdpr = 1"
@@ -35,8 +48,8 @@ class HttpSettingsSpec extends BaseSpec {
bidRequest.regs.gdpr = 1
and: "Prepare default account response with gdpr = 0"
- def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(bidRequest?.site?.publisher?.id)
- httpSettings.setResponse(bidRequest?.site?.publisher?.id, httpSettingsResponse)
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(bidRequest.accountId)
+ httpSettings.setResponse(bidRequest.accountId, httpSettingsResponse)
when: "PBS processes auction request"
def response = prebidServerService.sendAuctionRequest(bidRequest)
@@ -51,7 +64,32 @@ class HttpSettingsSpec extends BaseSpec {
assert bidder.getRequestCount(bidRequest.id) == 1
and: "There should be only one account request"
- assert httpSettings.getRequestCount(bidRequest?.site?.publisher?.id) == 1
+ assert httpSettings.getRequestCount(bidRequest.accountId) == 1
+ }
+
+ def "PBS should take account information from http data source on auction request when rfc3986 enabled"() {
+ given: "Get basic BidRequest with generic bidder and set gdpr = 1"
+ def bidRequest = BidRequest.defaultBidRequest
+ bidRequest.regs.gdpr = 1
+
+ and: "Prepare default account response with gdpr = 0"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(bidRequest.accountId)
+ httpSettings.setRfcResponse(bidRequest.accountId, httpSettingsResponse)
+
+ when: "PBS processes auction request"
+ def response = prebidServerServiceWithRfc.sendAuctionRequest(bidRequest)
+
+ then: "Response should contain basic fields"
+ assert response.id
+ assert response.seatbid?.size() == 1
+ assert response.seatbid.first().seat == GENERIC
+ assert response.seatbid?.first()?.bid?.size() == 1
+
+ and: "There should be only one call to bidder"
+ assert bidder.getRequestCount(bidRequest.id) == 1
+
+ and: "There should be only one account request"
+ assert httpSettings.getRfcRequestCount(bidRequest.accountId) == 1
}
def "PBS should take account information from http data source on AMP request"() {
@@ -84,6 +122,36 @@ class HttpSettingsSpec extends BaseSpec {
assert !response.ext?.debug?.httpcalls?.isEmpty()
}
+ def "PBS should take account information from http data source on AMP request when rfc3986 enabled"() {
+ given: "Default AmpRequest"
+ def ampRequest = AmpRequest.defaultAmpRequest
+
+ and: "Get basic stored request and set gdpr = 1"
+ def ampStoredRequest = BidRequest.defaultBidRequest
+ ampStoredRequest.site.publisher.id = ampRequest.account
+ ampStoredRequest.regs.gdpr = 1
+
+ and: "Save storedRequest into DB"
+ def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest)
+ storedRequestDao.save(storedRequest)
+
+ and: "Prepare default account response with gdpr = 0"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(ampRequest.account.toString())
+ httpSettings.setRfcResponse(ampRequest.account.toString(), httpSettingsResponse)
+
+ when: "PBS processes amp request"
+ def response = prebidServerServiceWithRfc.sendAmpRequest(ampRequest)
+
+ then: "Response should contain httpcalls"
+ assert !response.ext?.debug?.httpcalls?.isEmpty()
+
+ and: "There should be only one account request"
+ assert httpSettings.getRfcRequestCount(ampRequest.account.toString()) == 1
+
+ then: "Response should contain targeting"
+ assert !response.ext?.debug?.httpcalls?.isEmpty()
+ }
+
def "PBS should take account information from http data source on event request"() {
given: "Default EventRequest"
def eventRequest = EventRequest.defaultEventRequest
@@ -103,6 +171,25 @@ class HttpSettingsSpec extends BaseSpec {
assert httpSettings.getRequestCount(eventRequest.accountId.toString()) == 1
}
+ def "PBS should take account information from http data source on event request when rfc3986 enabled"() {
+ given: "Default EventRequest"
+ def eventRequest = EventRequest.defaultEventRequest
+
+ and: "Prepare default account response"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(eventRequest.accountId.toString())
+ httpSettings.setRfcResponse(eventRequest.accountId.toString(), httpSettingsResponse)
+
+ when: "PBS processes event request"
+ def responseBody = prebidServerServiceWithRfc.sendEventRequest(eventRequest)
+
+ then: "Event response should contain and corresponding content-type"
+ assert responseBody ==
+ ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png")
+
+ and: "There should be only one account request"
+ assert httpSettings.getRfcRequestCount(eventRequest.accountId.toString()) == 1
+ }
+
def "PBS should take account information from http data source on setuid request"() {
given: "Pbs config with adapters.generic.usersync.redirect.*"
def pbsConfig = PbsConfig.httpSettingsConfig +
@@ -137,6 +224,42 @@ class HttpSettingsSpec extends BaseSpec {
pbsServiceFactory.removeContainer(pbsConfig)
}
+ def "PBS should take account information from http data source on setuid request when rfc3986 enabled"() {
+ given: "Pbs config with adapters.generic.usersync.redirect.*"
+ def pbsConfig = new HashMap<>(PbsConfig.httpSettingsConfig) +
+ ['settings.http.endpoint': "${networkServiceContainer.rootUri}${HttpSettings.rfcEndpoint}".toString(),
+ 'settings.http.rfc3986-compatible': 'true',
+ 'adapters.generic.usersync.redirect.url' : "$networkServiceContainer.rootUri/generic-usersync&redir={{redirect_url}}".toString(),
+ 'adapters.generic.usersync.redirect.support-cors' : 'false',
+ 'adapters.generic.usersync.redirect.format-override': 'blank']
+ def prebidServerService = pbsServiceFactory.getService(pbsConfig)
+
+ and: "Get default SetuidRequest and set account, gdpr=1 "
+ def request = SetuidRequest.defaultSetuidRequest
+ request.gdpr = 1
+ request.account = PBSUtils.randomNumber.toString()
+ def uidsCookie = UidsCookie.defaultUidsCookie
+
+ and: "Prepare default account response"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(request.account)
+ httpSettings.setRfcResponse(request.account, httpSettingsResponse)
+
+ when: "PBS processes setuid request"
+ def response = prebidServerService.sendSetUidRequest(request, uidsCookie)
+
+ then: "Response should contain tempUIDs cookie"
+ assert !response.uidsCookie.uids
+ assert response.uidsCookie.tempUIDs
+ assert response.responseBody ==
+ ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png")
+
+ and: "There should be only one account request"
+ assert httpSettings.getRfcRequestCount(request.account) == 1
+
+ cleanup: "Stop and remove pbs container"
+ pbsServiceFactory.removeContainer(pbsConfig)
+ }
+
def "PBS should take account information from http data source on vtrack request"() {
given: "Default VtrackRequest"
String payload = PBSUtils.randomString
@@ -162,6 +285,31 @@ class HttpSettingsSpec extends BaseSpec {
assert prebidCacheRequest.contains("/event?t=imp&b=${request.puts[0].bidid}&a=$accountId&bidder=${request.puts[0].bidder}")
}
+ def "PBS should take account information from http data source on vtrack request when rfc3986 enabled"() {
+ given: "Default VtrackRequest"
+ String payload = PBSUtils.randomString
+ def request = VtrackRequest.getDefaultVtrackRequest(encodeXml(Vast.getDefaultVastModel(payload)))
+ def accountId = PBSUtils.randomNumber.toString()
+
+ and: "Prepare default account response"
+ def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(accountId)
+ httpSettings.setRfcResponse(accountId, httpSettingsResponse)
+
+ when: "PBS processes vtrack request"
+ def response = prebidServerServiceWithRfc.sendVtrackRequest(request, accountId)
+
+ then: "Response should contain uid"
+ assert response.responses[0]?.uuid
+
+ and: "There should be only one account request and pbc request"
+ assert httpSettings.getRfcRequestCount(accountId.toString()) == 1
+ assert prebidCache.getXmlRequestCount(payload) == 1
+
+ and: "VastXml that was send to PrebidCache must contain event url"
+ def prebidCacheRequest = prebidCache.getXmlRecordedRequestsBody(payload)[0]
+ assert prebidCacheRequest.contains("/event?t=imp&b=${request.puts[0].bidid}&a=$accountId&bidder=${request.puts[0].bidder}")
+ }
+
def "PBS should return error if account settings isn't found"() {
given: "Default EventRequest"
def eventRequest = EventRequest.defaultEventRequest
@@ -174,4 +322,17 @@ class HttpSettingsSpec extends BaseSpec {
assert exception.statusCode == 401
assert exception.responseBody.contains("Account '$eventRequest.accountId' doesn't support events")
}
+
+ def "PBS should return error if account settings isn't found when rfc3986 enabled"() {
+ given: "Default EventRequest"
+ def eventRequest = EventRequest.defaultEventRequest
+
+ when: "PBS processes event request"
+ prebidServerServiceWithRfc.sendEventRequest(eventRequest)
+
+ then: "Request should fail with error"
+ def exception = thrown(PrebidServerException)
+ assert exception.statusCode == 401
+ assert exception.responseBody.contains("Account '$eventRequest.accountId' doesn't support events")
+ }
}
diff --git a/src/test/java/org/prebid/server/bidder/madsense/MadsenseBidderTest.java b/src/test/java/org/prebid/server/bidder/madsense/MadsenseBidderTest.java
new file mode 100644
index 00000000000..ac4933dc439
--- /dev/null
+++ b/src/test/java/org/prebid/server/bidder/madsense/MadsenseBidderTest.java
@@ -0,0 +1,368 @@
+package org.prebid.server.bidder.madsense;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iab.openrtb.request.Banner;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Device;
+import com.iab.openrtb.request.Imp;
+import com.iab.openrtb.request.Site;
+import com.iab.openrtb.request.Video;
+import com.iab.openrtb.response.Bid;
+import com.iab.openrtb.response.BidResponse;
+import com.iab.openrtb.response.SeatBid;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.prebid.server.VertxTest;
+import org.prebid.server.bidder.model.BidderBid;
+import org.prebid.server.bidder.model.BidderCall;
+import org.prebid.server.bidder.model.HttpRequest;
+import org.prebid.server.bidder.model.HttpResponse;
+import org.prebid.server.bidder.model.Result;
+import org.prebid.server.proto.openrtb.ext.ExtPrebid;
+import org.prebid.server.proto.openrtb.ext.request.madsense.ExtImpMadsense;
+import org.prebid.server.proto.openrtb.ext.response.BidType;
+import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.function.UnaryOperator;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.prebid.server.bidder.model.BidderError.Type;
+import static org.prebid.server.bidder.model.BidderError.badInput;
+import static org.prebid.server.bidder.model.BidderError.badServerResponse;
+import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER;
+import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE;
+import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER;
+import static org.prebid.server.util.HttpUtil.ORIGIN_HEADER;
+import static org.prebid.server.util.HttpUtil.REFERER_HEADER;
+import static org.prebid.server.util.HttpUtil.USER_AGENT_HEADER;
+import static org.prebid.server.util.HttpUtil.X_FORWARDED_FOR_HEADER;
+import static org.prebid.server.util.HttpUtil.X_OPENRTB_VERSION_HEADER;
+import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE;
+
+@ExtendWith(MockitoExtension.class)
+public class MadsenseBidderTest extends VertxTest {
+
+ private static final String ENDPOINT_URL = "https://ads.madsense.io/pbs";
+
+ private MadsenseBidder target;
+
+ @BeforeEach
+ public void setUp() {
+ target = new MadsenseBidder(ENDPOINT_URL, jacksonMapper);
+ }
+
+ @Test
+ public void creationShouldFailOnInvalidEndpointUrl() {
+ assertThatIllegalArgumentException().isThrownBy(() -> new MadsenseBidder("invalid_url", jacksonMapper));
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnErrorWhenFirstImpExtCouldNotBeParsed() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(imp -> imp
+ .banner(Banner.builder().build())
+ .ext(givenInvalidImpExt()));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).hasSize(1).containsExactly(badInput("Error parsing imp.ext parameters"));
+ assertThat(result.getValue()).isEmpty();
+ }
+
+ @Test
+ public void makeHttpRequestsShouldCreateSeparateRequestForEachBannerImp() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ builder -> builder.id("bannerImp1").banner(Banner.builder().build())
+ .ext(givenImpExt("testCompanyId")),
+ builder -> builder.id("bannerImp2").banner(Banner.builder().build())
+ .ext(givenImpExt("otherCompanyId")));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(2);
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getImpIds)
+ .containsExactlyInAnyOrder(Set.of("bannerImp1"), Set.of("bannerImp2"));
+
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getUri)
+ .containsOnly(ENDPOINT_URL + "?company_id=testCompanyId");
+ }
+
+ @Test
+ public void makeHttpRequestsShouldCreateOneRequestForAllVideoImps() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ builder -> builder.id("videoImp1").video(Video.builder().build())
+ .ext(givenImpExt("testCompanyId")),
+ builder -> builder.id("videoImp2").video(Video.builder().build())
+ .ext(givenImpExt("otherCompanyId")));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1);
+ assertThat(result.getValue().getFirst().getImpIds()).containsExactlyInAnyOrder("videoImp1", "videoImp2");
+ }
+
+ @Test
+ public void makeHttpRequestsShouldHandleMixedBannerAndVideoImps() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ builder -> builder.id("bannerImp1").banner(Banner.builder().build())
+ .ext(givenImpExt("testCompanyId")),
+ builder -> builder.id("videoImp1").video(Video.builder().build())
+ .ext(givenImpExt("otherCompanyId1")),
+ builder -> builder.id("bannerImp2").banner(Banner.builder().build())
+ .ext(givenImpExt("otherCompanyId2")),
+ builder -> builder.id("videoImp2").video(Video.builder().build())
+ .ext(givenImpExt("otherCompanyId3")));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(3)
+ .extracting(HttpRequest::getImpIds)
+ .containsExactly(Set.of("bannerImp1"), Set.of("bannerImp2"), Set.of("videoImp1", "videoImp2"));
+ }
+
+ @Test
+ public void makeHttpRequestsShouldUseTestCompanyIdWhenRequestTestIsOne() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ builder -> builder.banner(Banner.builder().build()).ext(givenImpExt("testCompanyId")))
+ .toBuilder()
+ .test(1)
+ .build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(HttpRequest::getUri)
+ .containsExactly(ENDPOINT_URL + "?company_id=test");
+ }
+
+ @Test
+ public void makeHttpRequestsShouldSetCorrectHeaders() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(imp -> imp.banner(Banner.builder().build()))
+ .toBuilder()
+ .device(Device.builder().ua("ua").ip("ip").ipv6("ipv6").build())
+ .site(Site.builder().domain("domain").ref("referrer").build())
+ .build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1).first()
+ .extracting(HttpRequest::getHeaders)
+ .satisfies(headers -> {
+ assertThat(headers.get(CONTENT_TYPE_HEADER)).isEqualTo(APPLICATION_JSON_CONTENT_TYPE);
+ assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE);
+ assertThat(headers.get(X_OPENRTB_VERSION_HEADER)).isEqualTo("2.6");
+ assertThat(headers.get(USER_AGENT_HEADER)).isEqualTo("ua");
+ assertThat(headers.get(X_FORWARDED_FOR_HEADER)).isEqualTo("ip");
+ assertThat(headers.get(ORIGIN_HEADER)).isEqualTo("domain");
+ assertThat(headers.get(REFERER_HEADER)).isEqualTo("referrer");
+ });
+ }
+
+ @Test
+ public void makeHttpRequestsShouldSetIpv6WhenIpIsMissing() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(imp -> imp.banner(Banner.builder().build()))
+ .toBuilder()
+ .device(Device.builder().ipv6("ipv6").ip(null).build())
+ .build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue().getFirst().getHeaders().get(X_FORWARDED_FOR_HEADER))
+ .isEqualTo("ipv6");
+ }
+
+ @Test
+ public void makeBidsShouldReturnErrorWhenResponseBodyCouldNotBeParsed() {
+ // given
+ final BidderCall httpCall = givenHttpCall("invalid_json");
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).hasSize(1)
+ .allSatisfy(error -> {
+ assertThat(error.getType()).isEqualTo(Type.bad_server_response);
+ assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token 'invalid_json'");
+ });
+ assertThat(result.getValue()).isEmpty();
+ }
+
+ @Test
+ public void makeBidsShouldReturnEmptyListWhenBidResponseIsNull() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(null));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).isEmpty();
+ }
+
+ @Test
+ public void makeBidsShouldReturnEmptyListWhenSeatBidIsEmpty() throws JsonProcessingException {
+ // given
+ final BidResponse bidResponseEmptySeatBid = BidResponse.builder().seatbid(emptyList()).build();
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(bidResponseEmptySeatBid));
+
+ // when
+ final Result> resultEmptySeatBid = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(resultEmptySeatBid.getErrors()).isEmpty();
+ assertThat(resultEmptySeatBid.getValue()).isEmpty();
+ }
+
+ @Test
+ public void makeBidsShouldReturnBannerBidSuccessfully() throws JsonProcessingException {
+ // given
+ final Bid bannerBid = Bid.builder().id("bidId1").impid("impId1").price(BigDecimal.ONE)
+ .adm("adm1").mtype(1).cat(singletonList("cat1")).build();
+ final BidResponse bidResponse = BidResponse.builder()
+ .cur("USD")
+ .seatbid(singletonList(SeatBid.builder().bid(singletonList(bannerBid)).build()))
+ .build();
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(bidResponse));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .containsExactly(BidderBid.of(bannerBid, BidType.banner, "USD"));
+ }
+
+ @Test
+ public void makeBidsShouldReturnVideoBidSuccessfullyWithVideoInfo() throws JsonProcessingException {
+ // given
+ final Bid videoBid = Bid.builder().id("bidId1").mtype(2).cat(singletonList("cat-video")).dur(30).build();
+ final BidResponse bidResponse = BidResponse.builder()
+ .cur("EUR")
+ .seatbid(singletonList(SeatBid.builder().bid(singletonList(videoBid)).build()))
+ .build();
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(bidResponse));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ final BidderBid expectedBidderBid = BidderBid.builder()
+ .bid(videoBid)
+ .type(BidType.video)
+ .bidCurrency("EUR")
+ .videoInfo(ExtBidPrebidVideo.of(30, "cat-video"))
+ .build();
+
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1).containsExactly(expectedBidderBid);
+ }
+
+ @Test
+ public void makeBidsShouldReturnErrorWhenBidMtypeIsMissing() throws JsonProcessingException {
+ // given
+ final Bid bid = Bid.builder().id("bidId1").impid("impId1").build();
+ final BidResponse bidResponse = BidResponse.builder()
+ .cur("USD")
+ .seatbid(singletonList(SeatBid.builder().bid(singletonList(bid)).build()))
+ .build();
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(bidResponse));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getValue()).isEmpty();
+ assertThat(result.getErrors()).hasSize(1)
+ .containsExactly(badServerResponse("Unsupported bid mediaType: null for impression: impId1"));
+ }
+
+ @Test
+ public void makeBidsShouldReturnErrorWhenBidMtypeIsUnsupported() throws JsonProcessingException {
+ // given
+ final Bid bid = Bid.builder().id("bidId1").impid("impId1").mtype(3).build();
+ final BidResponse bidResponse = BidResponse.builder()
+ .cur("USD")
+ .seatbid(singletonList(SeatBid.builder()
+ .bid(singletonList(bid)).build()))
+ .build();
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(bidResponse));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getValue()).isEmpty();
+ assertThat(result.getErrors()).hasSize(1)
+ .containsExactly(badServerResponse("Unsupported bid mediaType: 3 for impression: impId1"));
+ }
+
+ private static BidRequest givenBidRequest(UnaryOperator... impCustomizers) {
+ final List imps = Arrays.stream(impCustomizers)
+ .map(MadsenseBidderTest::givenImp)
+ .toList();
+ return BidRequest.builder().imp(imps).cur(singletonList("USD")).build();
+ }
+
+ private static Imp givenImp(UnaryOperator impCustomizer) {
+ return impCustomizer.apply(Imp.builder()
+ .id("test-imp-id")
+ .ext(givenImpExt("testCompanyId")))
+ .build();
+ }
+
+ private static ObjectNode givenImpExt(String companyId) {
+ return mapper.valueToTree(ExtPrebid.of(null, ExtImpMadsense.of(companyId)));
+ }
+
+ private static ObjectNode givenInvalidImpExt() {
+ return mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()));
+ }
+
+ private static BidderCall givenHttpCall(String body) {
+ return BidderCall.succeededHttp(
+ HttpRequest.builder().build(),
+ HttpResponse.of(200, null, body),
+ null);
+ }
+}
diff --git a/src/test/java/org/prebid/server/bidder/thetradedesk/TheTradeDeskBidderTest.java b/src/test/java/org/prebid/server/bidder/thetradedesk/TheTradeDeskBidderTest.java
index a292ac6d385..8c167590e4f 100644
--- a/src/test/java/org/prebid/server/bidder/thetradedesk/TheTradeDeskBidderTest.java
+++ b/src/test/java/org/prebid/server/bidder/thetradedesk/TheTradeDeskBidderTest.java
@@ -24,6 +24,7 @@
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
import org.prebid.server.proto.openrtb.ext.request.thetradedesk.ExtImpTheTradeDesk;
+import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -33,6 +34,7 @@
import static java.util.function.UnaryOperator.identity;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.tuple;
import static org.prebid.server.proto.openrtb.ext.response.BidType.banner;
import static org.prebid.server.proto.openrtb.ext.response.BidType.video;
import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative;
@@ -458,7 +460,7 @@ public void makeBidsShouldReturnVideoBid() throws JsonProcessingException {
}
@Test
- public void makeBidsShouldThrowErrorWhenMediaTypeIsMissing() throws JsonProcessingException {
+ public void makeBidsShouldReturnErrorWhenMediaTypeIsMissing() throws JsonProcessingException {
// given
final BidderCall httpCall = givenHttpCall(
givenBidResponse(bidBuilder -> bidBuilder.impid("123")));
@@ -469,7 +471,234 @@ public void makeBidsShouldThrowErrorWhenMediaTypeIsMissing() throws JsonProcessi
// then
assertThat(result.getValue()).isEmpty();
assertThat(result.getErrors()).hasSize(1)
- .containsOnly(BidderError.badServerResponse("unsupported mtype: null"));
+ .containsOnly(BidderError.badServerResponse("could not define media type for impression: 123"));
+ }
+
+ @Test
+ public void makeBidsShouldReturnValidBidsAndErrorsForMixedMediaTypes() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ mapper.writeValueAsString(BidResponse.builder()
+ .cur("USD")
+ .seatbid(singletonList(SeatBid.builder()
+ .bid(Arrays.asList(
+ Bid.builder().mtype(1).impid("valid1").build(), // valid banner
+ Bid.builder().mtype(3).impid("invalid1").build(), // invalid mtype
+ Bid.builder().mtype(2).impid("valid2").build(), // valid video
+ Bid.builder().mtype(null).impid("invalid2").build() // null mtype
+ ))
+ .build()))
+ .build()));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getValue()).hasSize(2)
+ .extracting(bidderBid -> bidderBid.getBid().getImpid())
+ .containsExactly("valid1", "valid2");
+ assertThat(result.getErrors()).hasSize(2)
+ .containsExactly(
+ BidderError.badServerResponse("could not define media type for impression: invalid1"),
+ BidderError.badServerResponse("could not define media type for impression: invalid2"));
+ }
+
+ @Test
+ public void makeBidsShouldReplacePriceMacroInNurlAndAdmWithBidPrice() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidResponse(bidBuilder -> bidBuilder
+ .mtype(1)
+ .impid("123")
+ .price(BigDecimal.valueOf(1.23))
+ .nurl("http://example.com/nurl?price=${AUCTION_PRICE}")
+ .adm("Price: ${AUCTION_PRICE}")));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getNurl, Bid::getAdm, Bid::getPrice)
+ .containsOnly(tuple("http://example.com/nurl?price=1.23", "Price: 1.23", BigDecimal.valueOf(1.23)));
+ }
+
+ @Test
+ public void makeBidsShouldReplacePriceMacroWithZeroWhenBidPriceIsNull() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidResponse(bidBuilder -> bidBuilder
+ .mtype(1)
+ .impid("123")
+ .price(null)
+ .nurl("http://example.com/nurl?price=${AUCTION_PRICE}")
+ .adm("Price: ${AUCTION_PRICE}")));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getNurl, Bid::getAdm)
+ .containsOnly(tuple("http://example.com/nurl?price=0", "Price: 0"));
+ }
+
+ @Test
+ public void makeBidsShouldReplacePriceMacroWithZeroWhenBidPriceIsZero() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidResponse(bidBuilder -> bidBuilder
+ .mtype(1)
+ .impid("123")
+ .price(BigDecimal.ZERO)
+ .nurl("http://example.com/nurl?price=${AUCTION_PRICE}")
+ .adm("Price: ${AUCTION_PRICE}")));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getNurl, Bid::getAdm)
+ .containsOnly(tuple("http://example.com/nurl?price=0", "Price: 0"));
+ }
+
+ @Test
+ public void makeBidsShouldReplacePriceMacroInNurlOnlyWhenAdmDoesNotContainMacro() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidResponse(bidBuilder -> bidBuilder
+ .mtype(1)
+ .impid("123")
+ .price(BigDecimal.valueOf(5.67))
+ .nurl("http://example.com/nurl?price=${AUCTION_PRICE}")
+ .adm("No macro here")));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getNurl, Bid::getAdm)
+ .containsOnly(tuple("http://example.com/nurl?price=5.67", "No macro here"));
+ }
+
+ @Test
+ public void makeBidsShouldReplacePriceMacroInAdmOnlyWhenNurlDoesNotContainMacro() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidResponse(bidBuilder -> bidBuilder
+ .mtype(1)
+ .impid("123")
+ .price(BigDecimal.valueOf(8.90))
+ .nurl("http://example.com/nurl")
+ .adm("Price: ${AUCTION_PRICE}")));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getNurl, Bid::getAdm)
+ .containsOnly(tuple("http://example.com/nurl", "Price: 8.9"));
+ }
+
+ @Test
+ public void makeBidsShouldNotReplacePriceMacroWhenNurlAndAdmDoNotContainMacro() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidResponse(bidBuilder -> bidBuilder
+ .mtype(1)
+ .impid("123")
+ .price(BigDecimal.valueOf(12.34))
+ .nurl("http://example.com/nurl")
+ .adm("No macro")));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getNurl, Bid::getAdm)
+ .containsOnly(tuple("http://example.com/nurl", "No macro"));
+ }
+
+ @Test
+ public void makeBidsShouldHandleNullNurlAndAdm() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidResponse(bidBuilder -> bidBuilder
+ .mtype(1)
+ .impid("123")
+ .price(BigDecimal.valueOf(15.00))
+ .nurl(null)
+ .adm(null)));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getNurl, Bid::getAdm)
+ .containsOnly(tuple(null, null));
+ }
+
+ @Test
+ public void makeBidsShouldReplaceMultiplePriceMacrosInSameField() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidResponse(bidBuilder -> bidBuilder
+ .mtype(1)
+ .impid("123")
+ .price(BigDecimal.valueOf(9.99))
+ .nurl("http://example.com/nurl?price=${AUCTION_PRICE}&backup_price=${AUCTION_PRICE}")
+ .adm("Price: ${AUCTION_PRICE}, Fallback: ${AUCTION_PRICE}")));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getNurl, Bid::getAdm)
+ .containsOnly(tuple("http://example.com/nurl?price=9.99&backup_price=9.99", "Price: 9.99, Fallback: 9.99"));
+ }
+
+ @Test
+ public void makeBidsShouldHandleLargeDecimalPrices() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidResponse(bidBuilder -> bidBuilder
+ .mtype(1)
+ .impid("123")
+ .price(new BigDecimal("123456789.123456789"))
+ .nurl("http://example.com/nurl?price=${AUCTION_PRICE}")
+ .adm("Price: ${AUCTION_PRICE}")));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getNurl, Bid::getAdm)
+ .containsOnly(tuple("http://example.com/nurl?price=123456789.123456789", "Price: 123456789.123456789"));
}
private String givenBidResponse(UnaryOperator bidCustomizer) throws JsonProcessingException {
@@ -508,5 +737,4 @@ private static ObjectNode impExt(String publisherId) {
private static ObjectNode impExt(String publisherId, String supplySourceId) {
return mapper.valueToTree(ExtPrebid.of(null, ExtImpTheTradeDesk.of(publisherId, supplySourceId)));
}
-
}
diff --git a/src/test/java/org/prebid/server/bidder/visx/VisxBidderTest.java b/src/test/java/org/prebid/server/bidder/visx/VisxBidderTest.java
index cd11937bd94..d3640d344b7 100644
--- a/src/test/java/org/prebid/server/bidder/visx/VisxBidderTest.java
+++ b/src/test/java/org/prebid/server/bidder/visx/VisxBidderTest.java
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.databind.node.TextNode;
import com.iab.openrtb.request.Banner;
import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Video;
import com.iab.openrtb.response.Bid;
@@ -22,16 +23,19 @@
import org.prebid.server.bidder.visx.model.VisxSeatBid;
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
import org.prebid.server.proto.openrtb.ext.request.visx.ExtImpVisx;
+import org.prebid.server.util.HttpUtil;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.function.UnaryOperator;
import static java.util.Collections.singletonList;
import static java.util.function.UnaryOperator.identity;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.tuple;
import static org.prebid.server.proto.openrtb.ext.response.BidType.banner;
import static org.prebid.server.proto.openrtb.ext.response.BidType.video;
@@ -68,6 +72,48 @@ public void makeHttpRequestsShouldNotModifyIncomingRequest() {
.containsExactly(bidRequest);
}
+ @Test
+ public void makeHttpRequestsShouldAddIp() {
+ // given
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(singletonList(Imp.builder()
+ .ext(mapper.valueToTree(ExtPrebid.of(null,
+ ExtImpVisx.of(123, Arrays.asList(10, 20)))))
+ .build()))
+ .device(Device.builder().ip("someIp").ipv6("ipv6").build())
+ .build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getValue())
+ .flatExtracting(res -> res.getHeaders().entries())
+ .extracting(Map.Entry::getKey, Map.Entry::getValue)
+ .contains(tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "someIp"));
+ }
+
+ @Test
+ public void makeHttpRequestsShouldAddIpv6IfIpIsNotPresent() {
+ // given
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(singletonList(Imp.builder()
+ .ext(mapper.valueToTree(ExtPrebid.of(null,
+ ExtImpVisx.of(123, Arrays.asList(10, 20)))))
+ .build()))
+ .device(Device.builder().ipv6("ipv6").build())
+ .build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getValue())
+ .flatExtracting(res -> res.getHeaders().entries())
+ .extracting(Map.Entry::getKey, Map.Entry::getValue)
+ .contains(tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "ipv6"));
+ }
+
@Test
public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
// given
@@ -122,7 +168,7 @@ public void makeBidsShouldReturnBidWithTypeBannerIfBannerIsPresent() throws Json
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).containsExactly(
- BidderBid.of(Bid.builder().id("id").impid("123").build(), banner, null));
+ BidderBid.of(Bid.builder().id("id").impid("123").build(), banner, "USD"));
}
@Test
@@ -138,7 +184,7 @@ public void makeBidsShouldReturnBidWithTypeBannerIfVideoIsPresentAndBannerIsAbse
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).containsExactly(
- BidderBid.of(Bid.builder().id("id").impid("123").build(), video, null));
+ BidderBid.of(Bid.builder().id("id").impid("123").build(), video, "USD"));
}
@Test
@@ -194,7 +240,7 @@ public void makeBidsShouldFavourBidExtMediaTypeToImpMediaTypeWhenPresent() throw
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsExactly(BidderBid.of(givenBid(identity()), banner, null));
+ .containsExactly(BidderBid.of(givenBid(identity()), banner, "USD"));
}
@Test
@@ -213,7 +259,7 @@ public void makeBidsShouldReturnImpMediaTypeWhenBidExtMediaTypeIsAbsent() throws
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).containsExactly(
- BidderBid.of(givenBid(bidBuilder -> bidBuilder.ext(null)), video, null));
+ BidderBid.of(givenBid(bidBuilder -> bidBuilder.ext(null)), video, "USD"));
}
@Test
@@ -235,7 +281,7 @@ public void makeBidsShouldReturnImpMediaTypeWhenBidExtMediaTypeIsInvalid() throw
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).containsExactly(
- BidderBid.of(givenBid(bidBuilder -> bidBuilder.ext(givenBidExt("123"))), video, null));
+ BidderBid.of(givenBid(bidBuilder -> bidBuilder.ext(givenBidExt("123"))), video, "USD"));
}
@Test
@@ -289,7 +335,8 @@ public void makeBidsShouldReturnCorrectBidderBid() throws JsonProcessingExceptio
.h(100)
.adomain(singletonList("adomain"))
.build(),
- video, null);
+ video,
+ "USD");
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).containsExactly(expected);
@@ -314,7 +361,7 @@ private static Imp givenImp(UnaryOperator impCustomizer) {
private static VisxResponse givenVisxResponse(UnaryOperator bidCustomizer, String seat) {
return VisxResponse.of(singletonList(VisxSeatBid.of(
- singletonList(bidCustomizer.apply(VisxBid.builder()).build()), seat)));
+ singletonList(bidCustomizer.apply(VisxBid.builder()).build()), seat)), "USD");
}
private static Bid givenBid(UnaryOperator bidCustomizer) {
diff --git a/src/test/java/org/prebid/server/bidder/zeta_global_ssp/ZetaGlobalSspBidderTest.java b/src/test/java/org/prebid/server/bidder/zeta_global_ssp/ZetaGlobalSspBidderTest.java
new file mode 100644
index 00000000000..886af999d74
--- /dev/null
+++ b/src/test/java/org/prebid/server/bidder/zeta_global_ssp/ZetaGlobalSspBidderTest.java
@@ -0,0 +1,221 @@
+package org.prebid.server.bidder.zeta_global_ssp;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Imp;
+import com.iab.openrtb.response.Bid;
+import com.iab.openrtb.response.BidResponse;
+import com.iab.openrtb.response.SeatBid;
+import org.junit.jupiter.api.Test;
+import org.prebid.server.VertxTest;
+import org.prebid.server.bidder.model.BidderBid;
+import org.prebid.server.bidder.model.BidderCall;
+import org.prebid.server.bidder.model.BidderError;
+import org.prebid.server.bidder.model.HttpRequest;
+import org.prebid.server.bidder.model.HttpResponse;
+import org.prebid.server.bidder.model.Result;
+import org.prebid.server.bidder.theadx.TheadxBidder;
+import org.prebid.server.proto.openrtb.ext.ExtPrebid;
+import org.prebid.server.proto.openrtb.ext.request.zeta_global_ssp.ExtImpZetaGlobalSSP;
+import org.prebid.server.proto.openrtb.ext.response.BidType;
+
+import java.util.List;
+import java.util.function.UnaryOperator;
+
+import static java.util.Collections.singletonList;
+import static java.util.function.UnaryOperator.identity;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.prebid.server.bidder.model.BidderError.Type.bad_server_response;
+import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER;
+import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE;
+import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER;
+import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE;
+
+public class ZetaGlobalSspBidderTest extends VertxTest {
+
+ private static final String ENDPOINT_URL = "https://test-url.com/{{AccountID}}";
+
+ private final ZetaGlobalSspBidder target = new ZetaGlobalSspBidder(ENDPOINT_URL, jacksonMapper);
+
+ @Test
+ public void shouldFailOnBidderCreation() {
+ assertThatIllegalArgumentException().isThrownBy(() -> new TheadxBidder("invalid_url", jacksonMapper));
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnErrorIfImpExtMissing() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ givenImp(imp -> imp.id("imp1")
+ .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).hasSize(1)
+ .allSatisfy(error -> {
+ assertThat(error.getMessage()).contains("Missing bidder ext in impression with id: imp1");
+ assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input);
+ });
+ assertThat(result.getValue()).isEmpty();
+ }
+
+ @Test
+ public void makeHttpRequestsShouldCreateSingleRequestAndRemoveImpExt() {
+ // given
+ final Imp imp1 = givenImp(imp -> imp.id("imp1").ext(givenImpExt(11)));
+ final Imp imp2 = givenImp(imp -> imp.id("imp2").ext(givenImpExt(44)));
+ final BidRequest bidRequest = givenBidRequest(imp1, imp2);
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+ final HttpRequest httpRequest = result.getValue().getFirst();
+
+ // then
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getPayload)
+ .flatExtracting(BidRequest::getImp)
+ .extracting(Imp::getExt)
+ .containsExactly(null, givenImpExt(44));
+ assertThat(result.getErrors()).isEmpty();
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnExpectedHeaders() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(givenImp(identity()));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getValue()).hasSize(1).first()
+ .extracting(HttpRequest::getHeaders)
+ .satisfies(headers -> {
+ assertThat(headers.get(CONTENT_TYPE_HEADER)).isEqualTo(APPLICATION_JSON_CONTENT_TYPE);
+ assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE);
+ });
+ assertThat(result.getErrors()).isEmpty();
+ }
+
+ @Test
+ public void makeHttpRequestsShouldResolveMacroInEndpointUrl() {
+ // given
+ final Imp imp1 = givenImp(imp -> imp.id("imp1").ext(givenImpExt(11)));
+ final BidRequest bidRequest = givenBidRequest(imp1);
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(HttpRequest::getUri)
+ .containsExactly("https://test-url.com/11");
+ }
+
+ @Test
+ public void makeBidsShouldReturnErrorIfResponseBodyInvalid() {
+ // given
+ final BidderCall httpCall = givenHttpCall("invalid-response-body");
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getValue()).isEmpty();
+ assertThat(result.getErrors()).hasSize(1)
+ .allSatisfy(error -> {
+ assertThat(error.getMessage()).contains("Failed to decode:");
+ assertThat(error.getType()).isEqualTo(bad_server_response);
+ });
+ }
+
+ @Test
+ public void makeBidsShouldReturnEmptyListIfSeatBidIsNullOrEmpty() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall =
+ givenHttpCall(mapper.writeValueAsString(BidResponse.builder().cur("USD").build()));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).isEmpty();
+ }
+
+ @Test
+ public void makeBidsShouldReturnErrorIfCannotResolveBidType() throws JsonProcessingException {
+ // given
+ final Bid bid = givenBid("imp1", mapper.createObjectNode());
+ final BidderCall httpCall =
+ givenHttpCall(mapper.writeValueAsString(givenBidResponse(List.of(bid))));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getValue()).isEmpty();
+ assertThat(result.getErrors()).hasSize(1)
+ .allSatisfy(error -> {
+ assertThat(error.getMessage()).contains("Failed to parse impression \"imp1\" mediatype");
+ assertThat(error.getType()).isEqualTo(bad_server_response);
+ });
+ }
+
+ @Test
+ public void makeBidsShouldReturnBannerBidIfTypeParsedProperly() throws JsonProcessingException {
+ // given
+ final ObjectNode extWithPrebidType = mapper.createObjectNode();
+ extWithPrebidType.putObject("prebid").put("type", "banner");
+ final Bid validBid = givenBid("imp1", extWithPrebidType);
+
+ final BidResponse bidResponse = givenBidResponse(List.of(validBid));
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(bidResponse));
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1);
+ final BidderBid bidderBid = result.getValue().getFirst();
+ assertThat(bidderBid.getBid().getImpid()).isEqualTo("imp1");
+ assertThat(bidderBid.getType()).isEqualTo(BidType.banner);
+ assertThat(bidderBid.getBidCurrency()).isEqualTo("USD");
+ }
+
+ private static BidRequest givenBidRequest(Imp... imps) {
+ return BidRequest.builder().imp(List.of(imps)).build();
+ }
+
+ private static Imp givenImp(UnaryOperator impCustomizer) {
+ return impCustomizer.apply(Imp.builder().id("imp_id").ext(givenImpExt(11))).build();
+ }
+
+ private static ObjectNode givenImpExt(Integer sid) {
+ return mapper.valueToTree(ExtPrebid.of(null, ExtImpZetaGlobalSSP.of(sid)));
+ }
+
+ private static BidderCall givenHttpCall(String body) {
+ return BidderCall.succeededHttp(
+ HttpRequest.builder().payload(null).build(),
+ HttpResponse.of(200, null, body),
+ null);
+ }
+
+ private static Bid givenBid(String impId, ObjectNode ext) {
+ return Bid.builder().impid(impId).ext(ext).build();
+ }
+
+ private static BidResponse givenBidResponse(List bids) {
+ return BidResponse.builder()
+ .cur("USD")
+ .seatbid(singletonList(SeatBid.builder().bid(bids).build()))
+ .build();
+ }
+
+}
diff --git a/src/test/java/org/prebid/server/it/MadsenseTest.java b/src/test/java/org/prebid/server/it/MadsenseTest.java
new file mode 100644
index 00000000000..fd7366fafb6
--- /dev/null
+++ b/src/test/java/org/prebid/server/it/MadsenseTest.java
@@ -0,0 +1,33 @@
+package org.prebid.server.it;
+
+import io.restassured.response.Response;
+import org.json.JSONException;
+import org.junit.jupiter.api.Test;
+import org.prebid.server.model.Endpoint;
+
+import java.io.IOException;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static java.util.Collections.singletonList;
+
+public class MadsenseTest extends IntegrationTest {
+
+ @Test
+ public void openrtb2AuctionShouldRespondWithBidsFromMadsense() throws IOException, JSONException {
+ // given
+ WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/madsense-exchange"))
+ .withRequestBody(equalToJson(jsonFrom("openrtb2/madsense/test-madsense-bid-request.json")))
+ .willReturn(aResponse().withBody(jsonFrom("openrtb2/madsense/test-madsense-bid-response.json"))));
+
+ // when
+ final Response response = responseFor("openrtb2/madsense/test-auction-madsense-request.json",
+ Endpoint.openrtb2_auction);
+
+ // then
+ assertJsonEquals("openrtb2/madsense/test-auction-madsense-response.json", response,
+ singletonList("madsense"));
+ }
+}
diff --git a/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java
index e3076ddbdfd..60627cf2571 100644
--- a/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java
+++ b/src/test/java/org/prebid/server/settings/HttpApplicationSettingsTest.java
@@ -2,6 +2,8 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import io.vertx.core.Future;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URIBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -22,12 +24,14 @@
import org.prebid.server.vertx.httpclient.HttpClient;
import org.prebid.server.vertx.httpclient.model.HttpClientResponse;
+import java.net.URISyntaxException;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.TimeoutException;
import static java.util.Arrays.asList;
@@ -49,10 +53,10 @@
@ExtendWith(MockitoExtension.class)
public class HttpApplicationSettingsTest extends VertxTest {
- private static final String ENDPOINT = "http://stored-requests";
- private static final String AMP_ENDPOINT = "http://amp-stored-requests";
- private static final String VIDEO_ENDPOINT = "http://video-stored-requests";
- private static final String CATEGORY_ENDPOINT = "http://category-requests";
+ private static final String ENDPOINT = "http://stored-requests.com/something?id=1";
+ private static final String AMP_ENDPOINT = "http://amp-stored-requests.com/something?id=2";
+ private static final String VIDEO_ENDPOINT = "http://video-stored-requests.com/something?id=3";
+ private static final String CATEGORY_ENDPOINT = "http://category-requests.com/something";
@Mock(strictness = LENIENT)
private HttpClient httpClient;
@@ -65,7 +69,7 @@ public class HttpApplicationSettingsTest extends VertxTest {
@BeforeEach
public void setUp() {
httpApplicationSettings = new HttpApplicationSettings(httpClient, jacksonMapper, ENDPOINT, AMP_ENDPOINT,
- VIDEO_ENDPOINT, CATEGORY_ENDPOINT);
+ VIDEO_ENDPOINT, CATEGORY_ENDPOINT, false);
final Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
final TimeoutFactory timeoutFactory = new TimeoutFactory(clock);
@@ -77,7 +81,7 @@ public void setUp() {
public void creationShouldFailsOnInvalidEndpoint() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new HttpApplicationSettings(httpClient, jacksonMapper, "invalid_url", AMP_ENDPOINT,
- VIDEO_ENDPOINT, CATEGORY_ENDPOINT))
+ VIDEO_ENDPOINT, CATEGORY_ENDPOINT, false))
.withMessage("URL supplied is not valid: invalid_url");
}
@@ -85,7 +89,7 @@ public void creationShouldFailsOnInvalidEndpoint() {
public void creationShouldFailsOnInvalidAmpEndpoint() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new HttpApplicationSettings(httpClient, jacksonMapper, ENDPOINT, "invalid_url",
- VIDEO_ENDPOINT, CATEGORY_ENDPOINT))
+ VIDEO_ENDPOINT, CATEGORY_ENDPOINT, false))
.withMessage("URL supplied is not valid: invalid_url");
}
@@ -93,7 +97,7 @@ public void creationShouldFailsOnInvalidAmpEndpoint() {
public void creationShouldFailsOnInvalidVideoEndpoint() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new HttpApplicationSettings(httpClient, jacksonMapper, ENDPOINT, AMP_ENDPOINT,
- "invalid_url", CATEGORY_ENDPOINT))
+ "invalid_url", CATEGORY_ENDPOINT, false))
.withMessage("URL supplied is not valid: invalid_url");
}
@@ -118,7 +122,40 @@ public void getAccountByIdShouldReturnFetchedAccount() throws JsonProcessingExce
assertThat(future.result().getId()).isEqualTo("someId");
assertThat(future.result().getAuction().getPriceGranularity()).isEqualTo("testPriceGranularity");
- verify(httpClient).get(eq("http://stored-requests?account-ids=[\"someId\"]"), any(),
+ verify(httpClient).get(
+ eq("http://stored-requests.com/something?id=1&account-ids=%5B%22someId%22%5D"),
+ any(),
+ anyLong());
+ }
+
+ @Test
+ public void getAccountByIdShouldReturnFetchedAccountWithRfc3986CompatibleParams() throws JsonProcessingException {
+ // given
+ givenHttpClientReturnsResponse(200, null);
+ httpApplicationSettings = new HttpApplicationSettings(httpClient, jacksonMapper,
+ ENDPOINT, AMP_ENDPOINT, VIDEO_ENDPOINT, CATEGORY_ENDPOINT, true);
+
+ final Account account = Account.builder()
+ .id("someId")
+ .auction(AccountAuctionConfig.builder()
+ .priceGranularity("testPriceGranularity")
+ .build())
+ .privacy(AccountPrivacyConfig.builder().build())
+ .build();
+ final HttpAccountsResponse response = HttpAccountsResponse.of(Collections.singletonMap("someId", account));
+ givenHttpClientReturnsResponse(200, mapper.writeValueAsString(response));
+
+ // when
+ final Future future = httpApplicationSettings.getAccountById("someId", timeout);
+
+ // then
+ assertThat(future.succeeded()).isTrue();
+ assertThat(future.result().getId()).isEqualTo("someId");
+ assertThat(future.result().getAuction().getPriceGranularity()).isEqualTo("testPriceGranularity");
+
+ verify(httpClient).get(
+ eq("http://stored-requests.com/something?id=1&account-id=someId"),
+ any(),
anyLong());
}
@@ -234,8 +271,11 @@ public void getStoredDataShouldSendHttpRequestWithExpectedNewParams() {
new HashSet<>(asList("id3", "id4")), timeout);
// then
- verify(httpClient).get(eq("http://stored-requests?request-ids=[\"id2\",\"id1\"]&imp-ids=[\"id4\",\"id3\"]"),
- any(), anyLong());
+ verify(httpClient).get(
+ eq("http://stored-requests.com/something"
+ + "?id=1&request-ids=%5B%22id2%22%2C%22id1%22%5D&imp-ids=%5B%22id4%22%2C%22id3%22%5D"),
+ any(),
+ anyLong());
}
@Test
@@ -243,16 +283,45 @@ public void getStoredDataShouldSendHttpRequestWithExpectedAppendedParams() {
// given
givenHttpClientReturnsResponse(200, null);
httpApplicationSettings = new HttpApplicationSettings(httpClient, jacksonMapper,
- "http://some-domain?param1=value1", AMP_ENDPOINT, VIDEO_ENDPOINT, CATEGORY_ENDPOINT);
+ "http://some-domain.com?param1=value1", AMP_ENDPOINT, VIDEO_ENDPOINT, CATEGORY_ENDPOINT, false);
// when
httpApplicationSettings.getStoredData(null, singleton("id1"), singleton("id2"), timeout);
// then
- verify(httpClient).get(eq("http://some-domain?param1=value1&request-ids=[\"id1\"]&imp-ids=[\"id2\"]"), any(),
+ verify(httpClient).get(
+ eq("http://some-domain.com?param1=value1&request-ids=%5B%22id1%22%5D&imp-ids=%5B%22id2%22%5D"),
+ any(),
anyLong());
}
+ @Test
+ public void getStoredDataShouldSendHttpRequestWithRfc3986CompatibleParams() throws URISyntaxException {
+ // given
+ givenHttpClientReturnsResponse(200, null);
+ httpApplicationSettings = new HttpApplicationSettings(httpClient, jacksonMapper,
+ ENDPOINT, AMP_ENDPOINT, VIDEO_ENDPOINT, CATEGORY_ENDPOINT, true);
+
+ // when
+ httpApplicationSettings.getStoredData(null, Set.of("id1", "id2"), Set.of("id1", "id2"), timeout);
+
+ // then
+ final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class);
+ verify(httpClient).get(captor.capture(), any(), anyLong());
+
+ final URIBuilder uriBuilder = new URIBuilder(captor.getValue());
+ assertThat(uriBuilder.getHost()).isEqualTo("stored-requests.com");
+ assertThat(uriBuilder.getPath()).isEqualTo("/something");
+ assertThat(uriBuilder.getQueryParams())
+ .extracting(NameValuePair::getName, NameValuePair::getValue)
+ .containsExactlyInAnyOrder(
+ tuple("id", "1"),
+ tuple("request-id", "id1"),
+ tuple("request-id", "id2"),
+ tuple("imp-id", "id1"),
+ tuple("imp-id", "id2"));
+ }
+
@Test
public void getStoredDataShouldReturnResultWithErrorIfHttpClientFails() {
// given
@@ -416,7 +485,7 @@ public void getCategoriesShouldBuildUrlFromEndpointAdServerAndPublisher() {
// then
final ArgumentCaptor urlArgumentCaptor = ArgumentCaptor.forClass(String.class);
verify(httpClient).get(urlArgumentCaptor.capture(), anyLong());
- assertThat(urlArgumentCaptor.getValue()).isEqualTo("http://category-requests/primaryAdServer/publisher.json");
+ assertThat(urlArgumentCaptor.getValue()).isEqualTo("http://category-requests.com/something/primaryAdServer/publisher.json");
}
@Test
@@ -431,7 +500,8 @@ public void getCategoriesShouldBuildUrlFromEndpointAdServer() {
// then
final ArgumentCaptor urlArgumentCaptor = ArgumentCaptor.forClass(String.class);
verify(httpClient).get(urlArgumentCaptor.capture(), anyLong());
- assertThat(urlArgumentCaptor.getValue()).isEqualTo("http://category-requests/primaryAdServer.json");
+ assertThat(urlArgumentCaptor.getValue())
+ .isEqualTo("http://category-requests.com/something/primaryAdServer.json");
}
@Test
@@ -447,7 +517,7 @@ public void getCategoriesShouldReturnFailedFutureWithTimeoutException() {
// then
assertThat(result.failed()).isTrue();
assertThat(result.cause()).isInstanceOf(TimeoutException.class)
- .hasMessage("Failed to fetch categories from url 'http://category-requests/primaryAdServer.json'."
+ .hasMessage("Failed to fetch categories from url 'http://category-requests.com/something/primaryAdServer.json'."
+ " Reason: Timeout exceeded");
}
@@ -464,7 +534,7 @@ public void getCategoriesShouldReturnFailedFutureWhenResponseStatusIsNot200() {
// then
assertThat(result.failed()).isTrue();
assertThat(result.cause()).isInstanceOf(PreBidException.class)
- .hasMessage("Failed to fetch categories from url 'http://category-requests/primaryAdServer.json'."
+ .hasMessage("Failed to fetch categories from url 'http://category-requests.com/something/primaryAdServer.json'."
+ " Reason: Response status code is '400'");
}
@@ -481,7 +551,7 @@ public void getCategoriesShouldReturnFailedFutureWhenBodyIsNull() {
// then
assertThat(result.failed()).isTrue();
assertThat(result.cause()).isInstanceOf(PreBidException.class)
- .hasMessage("Failed to fetch categories from url 'http://category-requests/primaryAdServer.json'."
+ .hasMessage("Failed to fetch categories from url 'http://category-requests.com/something/primaryAdServer.json'."
+ " Reason: Response body is null or empty");
}
@@ -499,7 +569,7 @@ public void getCategoriesShouldReturnFailedFutureWhenBodyCantBeParsed() {
assertThat(result.failed()).isTrue();
assertThat(result.cause()).isInstanceOf(PreBidException.class)
.hasMessageStartingWith("Failed to fetch categories from url "
- + "'http://category-requests/primaryAdServer.json'. Reason: Failed to decode response body");
+ + "'http://category-requests.com/something/primaryAdServer.json'. Reason: Failed to decode response body");
}
@Test
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-auction-madsense-request.json b/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-auction-madsense-request.json
new file mode 100644
index 00000000000..6dd62c37a6d
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-auction-madsense-request.json
@@ -0,0 +1,23 @@
+{
+ "id": "request_id",
+ "imp": [
+ {
+ "id": "imp_id",
+ "banner": {
+ "w": 320,
+ "h": 250
+ },
+ "ext": {
+ "madsense": {
+ "company_id": "companyId"
+ }
+ }
+ }
+ ],
+ "tmax": 5000,
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-auction-madsense-response.json b/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-auction-madsense-response.json
new file mode 100644
index 00000000000..cab85202f9e
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-auction-madsense-response.json
@@ -0,0 +1,40 @@
+{
+ "id": "request_id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "bid_id",
+ "impid": "imp_id",
+ "exp": 300,
+ "price": 0.01,
+ "adid": "adid",
+ "cid": "cid",
+ "crid": "crid",
+ "mtype": 1,
+ "ext": {
+ "prebid": {
+ "type": "banner",
+ "meta": {
+ "adaptercode": "madsense"
+ }
+ },
+ "origbidcpm": 0.01
+ }
+ }
+ ],
+ "seat": "madsense",
+ "group": 0
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "madsense": "{{ madsense.response_time_ms }}"
+ },
+ "prebid": {
+ "auctiontimestamp": 0
+ },
+ "tmaxrequest": 5000
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-madsense-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-madsense-bid-request.json
new file mode 100644
index 00000000000..69b9eb55ae2
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-madsense-bid-request.json
@@ -0,0 +1,56 @@
+{
+ "id": "request_id",
+ "imp": [
+ {
+ "id": "imp_id",
+ "secure": 1,
+ "banner": {
+ "w": 320,
+ "h": 250
+ },
+ "ext": {
+ "tid": "${json-unit.any-string}",
+ "bidder": {
+ "company_id": "companyId"
+ }
+ }
+ }
+ ],
+ "source": {
+ "tid": "${json-unit.any-string}"
+ },
+ "site": {
+ "domain": "www.example.com",
+ "page": "http://www.example.com",
+ "publisher" : {
+ "domain" : "example.com"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "userAgent",
+ "ip": "193.168.244.1"
+ },
+ "at": 1,
+ "tmax": "${json-unit.any-number}",
+ "cur": [
+ "USD"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "ext": {
+ "prebid": {
+ "server": {
+ "externalurl": "http://localhost:8080",
+ "gvlid": 1,
+ "datacenter": "local",
+ "endpoint": "/openrtb2/auction"
+ }
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-madsense-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-madsense-bid-response.json
new file mode 100644
index 00000000000..0720cdf8a5e
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/madsense/test-madsense-bid-response.json
@@ -0,0 +1,19 @@
+{
+ "id": "request_id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "crid": "crid",
+ "adid": "adid",
+ "price": 0.01,
+ "id": "bid_id",
+ "impid": "imp_id",
+ "cid": "cid",
+ "mtype": 1
+ }
+ ],
+ "type": "banner"
+ }
+ ]
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-auction-zeta_global_ssp-request.json b/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-auction-zeta_global_ssp-request.json
index 0a3824d6e31..9ee42b27d18 100644
--- a/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-auction-zeta_global_ssp-request.json
+++ b/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-auction-zeta_global_ssp-request.json
@@ -12,7 +12,9 @@
]
},
"ext": {
- "zeta_global_ssp": {}
+ "zeta_global_ssp": {
+ "sid": 11
+ }
}
}
],
@@ -37,8 +39,6 @@
"tid": "tid"
},
"regs": {
- "ext": {
- "gdpr": 0
- }
+ "gdpr": 0
}
}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-zeta_global_ssp-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-zeta_global_ssp-bid-request.json
index 2608812c09e..2f86fcc4a79 100644
--- a/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-zeta_global_ssp-bid-request.json
+++ b/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-zeta_global_ssp-bid-request.json
@@ -11,11 +11,7 @@
}
]
},
- "secure": 1,
- "ext": {
- "tid": "${json-unit.any-string}",
- "bidder": {}
- }
+ "secure": 1
}
],
"site": {
@@ -47,9 +43,7 @@
"tid": "tid"
},
"regs": {
- "ext": {
- "gdpr": 0
- }
+ "gdpr": 0
},
"ext": {
"prebid": {
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-zeta_global_ssp-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-zeta_global_ssp-bid-response.json
index c31fabcb822..39d74ae42cd 100644
--- a/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-zeta_global_ssp-bid-response.json
+++ b/src/test/resources/org/prebid/server/it/openrtb2/zeta_global_ssp/test-zeta_global_ssp-bid-response.json
@@ -12,9 +12,15 @@
"cid": "cid001",
"adm": "adm001",
"h": 250,
- "w": 300
+ "w": 300,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
}
- ]
+ ],
+ "seat": "zeta_global_ssp"
}
]
}
diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties
index 01ff79b8691..49c87e9bb84 100644
--- a/src/test/resources/org/prebid/server/it/test-application.properties
+++ b/src/test/resources/org/prebid/server/it/test-application.properties
@@ -14,8 +14,6 @@ adapters.generic.aliases.cwire.enabled=true
adapters.generic.aliases.cwire.endpoint=http://localhost:8090/cwire-exchange
adapters.generic.aliases.infytv.enabled=true
adapters.generic.aliases.infytv.endpoint=http://localhost:8090/infytv-exchange
-adapters.generic.aliases.zeta_global_ssp.enabled=true
-adapters.generic.aliases.zeta_global_ssp.endpoint=http://localhost:8090/zeta_global_ssp-exchange
adapters.aceex.enabled=true
adapters.aceex.endpoint=http://localhost:8090/aceex-exchange
adapters.acuityads.enabled=true
@@ -353,6 +351,8 @@ adapters.lunamedia.enabled=true
adapters.lunamedia.endpoint=http://localhost:8090/lunamedia-exchange?pubid=
adapters.mabidder.enabled=true
adapters.mabidder.endpoint=http://localhost:8090/mabidder-exchange
+adapters.madsense.enabled=true
+adapters.madsense.endpoint=http://localhost:8090/madsense-exchange
adapters.madvertise.enabled=true
adapters.madvertise.endpoint=http://localhost:8090/madvertise-exchange
adapters.marsmedia.enabled=true
@@ -624,6 +624,8 @@ adapters.zentotem.enabled=true
adapters.zentotem.endpoint=http://localhost:8090/zentotem-exchange
adapters.zeroclickfraud.enabled=true
adapters.zeroclickfraud.endpoint=http://{{Host}}/zeroclickfraud-exchange?sid={{SourceId}}
+adapters.zeta_global_ssp.enabled=true
+adapters.zeta_global_ssp.endpoint=http://localhost:8090/zeta_global_ssp-exchange
adapters.aax.enabled=true
adapters.aax.endpoint=http://localhost:8090/aax-exchange
adapters.zmaticoo.enabled=true