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 Orbidder bidder #807

Merged
merged 4 commits into from
Sep 15, 2020
Merged
Changes from 1 commit
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
Next Next commit
Add OrbidderBidder and tests
AndriyPavlyuk committed Jul 13, 2020
commit 50ee3fa7b637b5c0bacabb9029b414b723a107f3
131 changes: 131 additions & 0 deletions src/main/java/org/prebid/server/bidder/orbidder/OrbidderBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package org.prebid.server.bidder.orbidder;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.http.HttpMethod;
import org.apache.commons.collections4.CollectionUtils;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpCall;
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.Result;
import org.prebid.server.exception.PreBidException;
import org.prebid.server.json.DecodeException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
import org.prebid.server.proto.openrtb.ext.request.orbidder.ExtImpOrbidder;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.HttpUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class OrbidderBidder implements Bidder<BidRequest> {

private static final TypeReference<ExtPrebid<?, ExtImpOrbidder>> ORBIDDER_EXT_TYPE_REFERENCE =
new TypeReference<ExtPrebid<?, ExtImpOrbidder>>() {
};

private static final String DEFAULT_BID_CURRENCY = "USD";

private final String endpointUrl;
private final JacksonMapper mapper;

public OrbidderBidder(String endpointUrl, JacksonMapper mapper) {
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
this.mapper = Objects.requireNonNull(mapper);
}

@Override
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
final List<BidderError> errors = new ArrayList<>();
final List<Imp> validImps = new ArrayList<>();

if (CollectionUtils.isEmpty(request.getImp())) {
AndriyPavlyuk marked this conversation as resolved.
Show resolved Hide resolved
errors.add(BidderError.badInput("No valid impressions in the bid request"));
return Result.of(Collections.emptyList(), errors);
}

for (Imp imp : request.getImp()) {
try {
parseImpExt(imp);
validImps.add(imp);
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}
}
final BidRequest outgoingRequest = request.toBuilder().imp(validImps).build();
final String body = mapper.encode(outgoingRequest);

return Result.of(Collections.singletonList(
HttpRequest.<BidRequest>builder()
.method(HttpMethod.POST)
.uri(endpointUrl)
.headers(HttpUtil.headers())
.payload(outgoingRequest)
.body(body)
.build()),
errors);
}

private void parseImpExt(Imp imp) {
try {
mapper.mapper().convertValue(imp.getExt(), ORBIDDER_EXT_TYPE_REFERENCE);
} catch (IllegalArgumentException e) {
throw new PreBidException(e.getMessage(), e);
}
}

@Override
public Result<List<BidderBid>> makeBids(HttpCall<BidRequest> httpCall, BidRequest bidRequest) {
final int statusCode = httpCall.getResponse().getStatusCode();
if (statusCode == HttpResponseStatus.NO_CONTENT.code()) {
return Result.of(Collections.emptyList(), Collections.emptyList());
} else if (statusCode == HttpResponseStatus.BAD_REQUEST.code()) {
return Result.emptyWithError(BidderError.badInput("Invalid request."));
} else if (statusCode == HttpResponseStatus.INTERNAL_SERVER_ERROR.code()) {
return Result.emptyWithError(BidderError.badInput("Server internal error."));
} else if (statusCode != HttpResponseStatus.OK.code()) {
return Result.emptyWithError(BidderError.badServerResponse(String.format("Unexpected HTTP status %s.",
statusCode)));
}

final BidResponse bidResponse;
try {
bidResponse = decodeBodyToBidResponse(httpCall);
} catch (PreBidException e) {
return Result.emptyWithError(BidderError.badServerResponse(e.getMessage()));
}

final List<BidderBid> bidderBids = bidResponse.getSeatbid().stream()
.map(SeatBid::getBid)
.flatMap(Collection::stream)
.map(bid -> BidderBid.of(bid, BidType.banner, DEFAULT_BID_CURRENCY))
.collect(Collectors.toList());
return Result.of(bidderBids, Collections.emptyList());
}

private BidResponse decodeBodyToBidResponse(HttpCall<BidRequest> httpCall) {
try {
return mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
} catch (DecodeException e) {
throw new PreBidException(e.getMessage(), e);
}
}

@Override
public Map<String, String> extractTargeting(ObjectNode ext) {
return Collections.emptyMap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.prebid.server.proto.openrtb.ext.request.orbidder;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;

import java.math.BigDecimal;

@Value
@AllArgsConstructor(staticName = "of")
public class ExtImpOrbidder {

@JsonProperty("accountId")
String accountId;

@JsonProperty("placementId")
String placementId;

@JsonProperty("bidfloor")
AndriyPavlyuk marked this conversation as resolved.
Show resolved Hide resolved
BigDecimal bidFloor;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.prebid.server.spring.config.bidder;

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.orbidder.OrbidderBidder;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
import org.prebid.server.spring.config.bidder.model.UsersyncConfigurationProperties;
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
import org.prebid.server.spring.config.bidder.util.BidderInfoCreator;
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
import org.prebid.server.spring.env.YamlPropertySourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
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 javax.validation.constraints.NotBlank;

@Configuration
@PropertySource(value = "classpath:/bidder-config/orbidder.yaml", factory = YamlPropertySourceFactory.class)
public class OrbidderConfiguration {

private static final String BIDDER_NAME = "orbidder";

@Value("${external-url}")
@NotBlank
private String externalUrl;

@Autowired
private JacksonMapper mapper;

@Autowired
@Qualifier("orbidderConfigurationProperties")
private BidderConfigurationProperties configProperties;

@Bean("orbidderConfigurationProperties")
@ConfigurationProperties("adapters.orbidder")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

@Bean
BidderDeps orbidderBidderDeps() {
final UsersyncConfigurationProperties usersync = configProperties.getUsersync();

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(configProperties)
.bidderInfo(BidderInfoCreator.create(configProperties))
.usersyncerCreator(UsersyncerCreator.create(usersync, externalUrl))
.bidderCreator(() -> new OrbidderBidder(configProperties.getEndpoint(), mapper))
.assemble();
}
}
23 changes: 23 additions & 0 deletions src/main/resources/bidder-config/orbidder.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
adapters:
orbidder:
enabled: false
endpoint: https://orbidder.otto.de/openrtb2
pbs-enforces-gdpr: true
pbs-enforces-ccpa: true
modifying-vast-xml-allowed: true
deprecated-names:
aliases:
meta-info:
maintainer-email: realtime-siggi@otto.de
app-media-types:
- banner
site-media-types:
- banner
supported-vendors:
vendor-id: 0
usersync:
url:
redirect-url:
cookie-family-name: orbidder
type: redirect
support-cors: false
24 changes: 24 additions & 0 deletions src/main/resources/static/bidder-params/orbidder.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Orbidder Adapter Params",
"description": "A schema which validates params accepted by the Orbidder adapter",

"type": "object",
"properties": {
"accountId": {
"type": "string",
"description": "The marketer's accountId."
},
"placementId": {
"type": "string",
"description": "The placementId of the ad unit."
},
"bidfloor": {
"type": "number",
"description": "The minimum CPM price in EUR.",
"minimum": 0
}
},

"required": ["accountId", "placementId"]
}
189 changes: 189 additions & 0 deletions src/test/java/org/prebid/server/bidder/orbidder/OrbidderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package org.prebid.server.bidder.orbidder;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import org.junit.Before;
import org.junit.Test;
import org.prebid.server.VertxTest;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpCall;
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.HttpResponse;
import org.prebid.server.bidder.model.Result;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

public class OrbidderTest extends VertxTest {

private static final String ENDPOINT_URL = "https://test.endpoint.com/";

private OrbidderBidder orbidderBidder;

@Before
public void setUp() {
orbidderBidder = new OrbidderBidder(ENDPOINT_URL, jacksonMapper);
}

@Test
public void creationShouldFailOnInvalidEndpointUrl() {
assertThatIllegalArgumentException().isThrownBy(() -> new OrbidderBidder("invalid_url", jacksonMapper));
}

@Test
public void makeHttpRequestsShouldReturnErrorIfImpressionListSizeIsZero() {
// given
final BidRequest bidRequest = BidRequest.builder()
.imp(emptyList())
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = orbidderBidder.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).hasSize(1)
.containsOnly(BidderError.badInput("No valid impressions in the bid request"));
}

@Test
public void makeBidsShouldReturnEmptyResultWhenResponseWithNoContent() {
// given
final HttpCall<BidRequest> httpCall = HttpCall
.success(null, HttpResponse.of(204, null, null), null);

// when
final Result<List<BidderBid>> result = orbidderBidder.makeBids(httpCall, null);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).isEmpty();
}

@Test
public void makeBidsShouldReturnEmptyResultWhenResponseStatusIsNotOk() {
// given
final HttpCall<BidRequest> httpCall = HttpCall
.success(null, HttpResponse.of(404, null, null), null);

// when
final Result<List<BidderBid>> result = orbidderBidder.makeBids(httpCall, null);

// then
assertThat(result.getErrors().get(0).getMessage()).startsWith("Unexpected HTTP status 404.");
assertThat(result.getErrors().get(0).getType()).isEqualTo(BidderError.Type.bad_server_response);
}

@Test
public void makeBidsShouldReturnEmptyResultWhenBadRequest() {
// given
final HttpCall<BidRequest> httpCall = HttpCall
.success(null, HttpResponse.of(400, null, null), null);

// when
final Result<List<BidderBid>> result = orbidderBidder.makeBids(httpCall, null);

// then
assertThat(result.getErrors().get(0).getMessage()).startsWith("Invalid request.");
assertThat(result.getErrors().get(0).getType()).isEqualTo(BidderError.Type.bad_input);
}

@Test
public void makeBidsShouldReturnEmptyResultWhenBadServerResponse() {
// given
final HttpCall<BidRequest> httpCall = HttpCall
.success(null, HttpResponse.of(500, null, null), null);

// when
final Result<List<BidderBid>> result = orbidderBidder.makeBids(httpCall, null);

// then
assertThat(result.getErrors().get(0).getMessage()).startsWith("Server internal error.");
assertThat(result.getErrors().get(0).getType()).isEqualTo(BidderError.Type.bad_input);
}

@Test
public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
// given
final HttpCall<BidRequest> httpCall = givenHttpCall("false");

// when
final Result<List<BidderBid>> result = orbidderBidder.makeBids(httpCall, null);

// then
assertThat(result.getErrors().get(0).getType()).isEqualTo(BidderError.Type.bad_server_response);
assertThat(result.getValue()).isEmpty();
}

@Test
public void makeBidsShouldReturnErrorsWhenSeatBidIsEmptyList() throws JsonProcessingException {
// given
final HttpCall<BidRequest> httpCall =
givenHttpCall(mapper.writeValueAsString(BidResponse.builder().seatbid(emptyList()).build()));

// when
final Result<List<BidderBid>> result = orbidderBidder.makeBids(httpCall, BidRequest.builder().build());

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result).isNotNull()
.extracting(Result::getValue, Result::getErrors)
.containsOnly(Collections.emptyList(), Collections.emptyList());
}

@Test
public void makeBidsShouldReturnErrorsWhenBidsEmptyList()
throws JsonProcessingException {
// given
final HttpCall<BidRequest> httpCall =
givenHttpCall(mapper.writeValueAsString(
BidResponse.builder()
.seatbid(singletonList(SeatBid.builder().bid(emptyList()).build()))
.build()));

// when
final Result<List<BidderBid>> result = orbidderBidder.makeBids(httpCall, BidRequest.builder().build());

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result).isNotNull()
.extracting(Result::getValue, Result::getErrors)
.containsOnly(Collections.emptyList(), Collections.emptyList());
}

@Test
public void extractTargetingShouldReturnEmptyMap() {
assertThat(orbidderBidder.extractTargeting(mapper.createObjectNode())).isEqualTo(emptyMap());
}

private static BidResponse givenBidResponse(Function<Bid.BidBuilder, Bid.BidBuilder> bidCustomizer) {
return BidResponse.builder()
.seatbid(singletonList(SeatBid.builder().bid(singletonList(bidCustomizer.apply(Bid.builder()).build()))
.build()))
.build();
}

private static HttpCall<BidRequest> givenHttpCall(String body) {
return HttpCall.success(
HttpRequest.<BidRequest>builder().payload(null).build(),
HttpResponse.of(200, null, body),
null);
}

private static Map<String, JsonNode> givenCustomParams(String key, Object values) {
return singletonMap(key, mapper.valueToTree(values));
}
}
57 changes: 57 additions & 0 deletions src/test/java/org/prebid/server/it/OrbidderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.prebid.server.it;

import io.restassured.response.Response;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
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 io.restassured.RestAssured.given;
import static java.util.Collections.singletonList;

@RunWith(SpringRunner.class)
public class OrbidderTest extends IntegrationTest {

@Test
public void openrtb2AuctionShouldRespondWithBidsFromOrbidder() throws IOException, JSONException {
// given
// OrbidderBidder bid response for imp 001
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/orbidder-exchange"))
.withHeader("Accept", equalTo("application/json"))
.withHeader("Content-Type", equalTo("application/json;charset=UTF-8"))
.withRequestBody(equalToJson(jsonFrom("openrtb2/orbidder/test-orbidder-bid-request.json")))
.willReturn(aResponse().withBody(jsonFrom("openrtb2/orbidder/test-orbidder-bid-response.json"))));

// pre-bid cache
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/cache"))
.withRequestBody(equalToJson(jsonFrom("openrtb2/orbidder/test-cache-orbidder-request.json")))
.willReturn(aResponse().withBody(jsonFrom("openrtb2/orbidder/test-cache-orbidder-response.json"))));

// when
final Response response = given(SPEC)
.header("Referer", "http://www.example.com")
.header("X-Forwarded-For", "193.168.244.1")
.header("User-Agent", "userAgent")
.header("Origin", "http://www.example.com")
//this uids cookie value stands for {"uids":{"orbidder":"OB-UID"}}
.cookie("uids", "eyJ1aWRzIjp7Im9yYmlkZGVyIjoiT0ItVUlEIn19")
.body(jsonFrom("openrtb2/orbidder/test-auction-orbidder-request.json"))
.post("/openrtb2/auction");

// then
final String expectedAuctionResponse = openrtbAuctionResponseFrom(
"openrtb2/orbidder/test-auction-orbidder-response.json",
response, singletonList("orbidder"));

JSONAssert.assertEquals(expectedAuctionResponse, response.asString(), JSONCompareMode.NON_EXTENSIBLE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"id": "tid",
"imp": [
{
"id": "impId001",
"tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"ext": {
"orbidder": {
"accountId": "orbidder-test",
"placementId": "test-placement",
"bidfloor": 0.1
}
}
}
],
"device": {
"pxratio": 4.2,
"dnt": 2,
"ip": "193.168.244.1",
"language": "en",
"ifa": "ifaId",
"ua": "userAgent"
},
"site": {
"domain": "example.com",
"ext": {
"amp": 0
},
"page": "http://www.example.com",
"publisher": {
"id": "publisherId"
}
},
"at": 1,
"tmax": 3000,
"cur": [
"USD"
],
"source": {
"fd": 1,
"tid": "tid"
},
"user": {
"ext": {
"consent": "consentValue",
"digitrust": {
"id": "id",
"keyv": 123,
"pref": 0
}
}
},
"regs": {
"ext": {
"gdpr": 0
}
},
"ext": {
"prebid": {
"currency": {
AndriyPavlyuk marked this conversation as resolved.
Show resolved Hide resolved
"rates": {
"EUR": {
"USD": 1.2406
},
"USD": {
"EUR": 0.8110
}
}
},
"targeting": {
"includebidderkeys": true,
"includewinners": true,
"pricegranularity": {
"precision": 2,
"ranges": [
{
"max": 20,
"increment": 0.1
}
]
}
},
"cache": {
"bids": {},
"vastxml": {
"ttlseconds": 120
}
},
"auctiontimestamp": 1000
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"id": "tid",
"seatbid": [
{
"bid": [
{
"adm": "<b>hi</b>",
"cid": "test_cid",
"crid": "test_banner_crid",
"ext": {
"bidder": {
"format": "BANNER"
},
"prebid": {
"type": "banner",
"targeting": {
"hb_bidder": "orbidder",
"hb_bidder_orbidder": "orbidder",
"hb_cache_host": "{{ cache.host }}",
"hb_cache_host_orbidder": "{{ cache.host }}",
"hb_cache_id": "f0ab9105-cb21-4e59-b433-70f5ad6671cb",
"hb_cache_id_orbidder": "f0ab9105-cb21-4e59-b433-70f5ad6671cb",
"hb_cache_path": "{{ cache.path }}",
"hb_cache_path_orbidder": "{{ cache.path }}",
"hb_pb": "0.00",
"hb_pb_orbidder": "0.00"
},
"cache": {
"bids": {
"cacheId": "f0ab9105-cb21-4e59-b433-70f5ad6671cb",
"url": "{{ cache.resource_url }}f0ab9105-cb21-4e59-b433-70f5ad6671cb"
}
}
}
},
"id": "1",
"impid": "imp123",
"price": 0.01
}
],
"group": 0,
"seat": "orbidder"
}
],
"cur": "USD",
"ext": {
"responsetimemillis": {
"cache": "{{ cache.response_time_ms }}",
"orbidder": "{{ orbidder.response_time_ms }}"
},
"prebid": {
"auctiontimestamp": 1000
},
"tmaxrequest": 3000
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"puts": [
{
"type": "json",
"value": {
"id": "1",
"impid": "imp123",
"price": 0.01,
"adm": "<b>hi</b>",
"cid": "test_cid",
"crid": "test_banner_crid",
"ext": {
"format": "BANNER"
}
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"responses": [
{
"uuid": "f0ab9105-cb21-4e59-b433-70f5ad6671cb"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{
"id": "tid",
"imp": [
{
"id": "impId001",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"tagid" : "2eb6bd58-865c-47ce-af7f-a918108c3fd2",
"ext": {
"bidder": {
"accountId": "orbidder-test",
"placementId": "test-placement",
"bidfloor": 0.1
}
}
}
],
"site": {
"domain": "example.com",
"page": "http://www.example.com",
"publisher": {
"id": "publisherId"
},
"ext": {
"amp": 0
}
},
"device": {
"ua": "userAgent",
"dnt": 2,
"ip": "193.168.244.1",
"pxratio": 4.2,
"language": "en",
"ifa": "ifaId"
},
"user": {
"buyeruid": "OB-UID",
"ext": {
"consent": "consentValue",
"digitrust": {
"id": "id",
"keyv": 123,
"pref": 0
}
}
},
"at": 1,
"tmax": 3000,
"cur": [
"USD"
],
"source": {
"fd": 1,
"tid": "tid"
},
"regs": {
"ext": {
"gdpr": 0
}
},
"ext": {
"prebid": {
"currency": {
"rates": {
"EUR": {
"USD": 1.2406
},
"USD": {
"EUR": 0.811
}
}
},
"targeting": {
"includebidderkeys": true,
"includewinners": true,
"pricegranularity": {
"precision": 2,
"ranges": [
{
"max": 20,
"increment": 0.1
}
]
}
},
"cache": {
"bids": {},
"vastxml": {
"ttlseconds": 120
}
},
"auctiontimestamp": 1000
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"id": "tid",
"seatbid": [
{
"bid": [
{
"adm": "<b>hi</b>",
"crid": "test_banner_crid",
"cid": "test_cid",
"impid": "imp123",
"id": "1",
"price": 0.01,
"ext": {
"format": "BANNER"
}
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -142,6 +142,10 @@ adapters.openx.enabled=true
adapters.openx.endpoint=http://localhost:8090/openx-exchange
adapters.openx.pbs-enforces-gdpr=true
adapters.openx.usersync.url=//openx-usersync
adapters.orbidder.enabled=true
adapters.orbidder.endpoint=http://localhost:8090/orbidder-exchange
adapters.orbidder.pbs-enforces-gdpr=true
adapters.orbidder.usersync.url=//orbidder-usersync
adapters.pubmatic.enabled=true
adapters.pubmatic.endpoint=http://localhost:8090/pubmatic-exchange
adapters.pubmatic.pbs-enforces-gdpr=true