diff --git a/.github/workflows/trivy-security-check.yml b/.github/workflows/trivy-security-check.yml
index 86f59bc74f0..f916a6158f7 100644
--- a/.github/workflows/trivy-security-check.yml
+++ b/.github/workflows/trivy-security-check.yml
@@ -20,7 +20,7 @@ jobs:
uses: actions/checkout@v5
- name: Run Trivy vulnerability scanner
- uses: aquasecurity/trivy-action@0.34.0
+ uses: aquasecurity/trivy-action@v0.35.0
with:
scan-type: 'fs'
scan-ref: '.'
diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml
index 73bb37dd71c..3cae168fb2b 100644
--- a/extra/bundle/pom.xml
+++ b/extra/bundle/pom.xml
@@ -5,7 +5,7 @@
org.prebid
prebid-server-aggregator
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
../../extra/pom.xml
diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml
index 5db3327672e..f39dc75d13e 100644
--- a/extra/modules/confiant-ad-quality/pom.xml
+++ b/extra/modules/confiant-ad-quality/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
confiant-ad-quality
diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml
index 55259e11268..c204ba80b42 100644
--- a/extra/modules/fiftyone-devicedetection/pom.xml
+++ b/extra/modules/fiftyone-devicedetection/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
fiftyone-devicedetection
diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml
index 4da76214d51..9acaeb06bc9 100644
--- a/extra/modules/greenbids-real-time-data/pom.xml
+++ b/extra/modules/greenbids-real-time-data/pom.xml
@@ -4,7 +4,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
greenbids-real-time-data
diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml
index ea53059e02b..f616bff759a 100644
--- a/extra/modules/live-intent-omni-channel-identity/pom.xml
+++ b/extra/modules/live-intent-omni-channel-identity/pom.xml
@@ -4,7 +4,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
live-intent-omni-channel-identity
diff --git a/extra/modules/optable-targeting/pom.xml b/extra/modules/optable-targeting/pom.xml
index f1abbf3b686..e202d7cfdd6 100644
--- a/extra/modules/optable-targeting/pom.xml
+++ b/extra/modules/optable-targeting/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
optable-targeting
diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml
index 37abf2ac4f9..0969d6f8a41 100644
--- a/extra/modules/ortb2-blocking/pom.xml
+++ b/extra/modules/ortb2-blocking/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
ortb2-blocking
diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml
index d124c73d8fc..a60dc337611 100644
--- a/extra/modules/pb-request-correction/pom.xml
+++ b/extra/modules/pb-request-correction/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
pb-request-correction
diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml
index b76895f94c5..b2d5b819821 100644
--- a/extra/modules/pb-response-correction/pom.xml
+++ b/extra/modules/pb-response-correction/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
pb-response-correction
diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml
index cc378b10294..51e5ab9f998 100644
--- a/extra/modules/pb-richmedia-filter/pom.xml
+++ b/extra/modules/pb-richmedia-filter/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
pb-richmedia-filter
diff --git a/extra/modules/pb-rule-engine/pom.xml b/extra/modules/pb-rule-engine/pom.xml
index 8e745d6efe4..61eddc94916 100644
--- a/extra/modules/pb-rule-engine/pom.xml
+++ b/extra/modules/pb-rule-engine/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
pb-rule-engine
diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml
index ca9e5258a2b..af20f6f2626 100644
--- a/extra/modules/pom.xml
+++ b/extra/modules/pom.xml
@@ -5,7 +5,7 @@
org.prebid
prebid-server-aggregator
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
../../extra/pom.xml
diff --git a/extra/modules/wurfl-devicedetection/pom.xml b/extra/modules/wurfl-devicedetection/pom.xml
index 7c8cbf6d071..12fe6d5f0da 100644
--- a/extra/modules/wurfl-devicedetection/pom.xml
+++ b/extra/modules/wurfl-devicedetection/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
wurfl-devicedetection
diff --git a/extra/pom.xml b/extra/pom.xml
index 3f34c1f23ed..1a43c1b8ace 100644
--- a/extra/pom.xml
+++ b/extra/pom.xml
@@ -4,7 +4,7 @@
org.prebid
prebid-server-aggregator
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
pom
diff --git a/pom.xml b/pom.xml
index c35b2867be9..8ef80350c78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.prebid
prebid-server-aggregator
- 3.41.0-SNAPSHOT
+ 3.42.0-SNAPSHOT
extra/pom.xml
diff --git a/src/main/java/org/prebid/server/bidder/mobkoi/MobkoiBidder.java b/src/main/java/org/prebid/server/bidder/mobkoi/MobkoiBidder.java
index dbf82da1b4f..699f9dca4c6 100644
--- a/src/main/java/org/prebid/server/bidder/mobkoi/MobkoiBidder.java
+++ b/src/main/java/org/prebid/server/bidder/mobkoi/MobkoiBidder.java
@@ -75,14 +75,14 @@ private ExtImpMobkoi parseExtImp(Imp imp) {
}
private Imp modifyImp(Imp firstImp, ExtImpMobkoi extImpMobkoi) {
- if (StringUtils.isNotBlank(firstImp.getTagid())) {
- return firstImp;
- }
-
if (StringUtils.isNotBlank(extImpMobkoi.getPlacementId())) {
return firstImp.toBuilder().tagid(extImpMobkoi.getPlacementId()).build();
}
+ if (StringUtils.isNotBlank(firstImp.getTagid())) {
+ return firstImp;
+ }
+
throw new PreBidException("invalid because it comes with neither request.imp[0].tagId nor "
+ "req.imp[0].ext.Bidder.placementId");
}
diff --git a/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java b/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java
index 3bc78b588b0..683695c4c7d 100644
--- a/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java
+++ b/src/main/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidder.java
@@ -1,10 +1,12 @@
package org.prebid.server.bidder.resetdigital;
+import com.fasterxml.jackson.core.type.TypeReference;
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 io.vertx.core.MultiMap;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.bidder.Bidder;
@@ -12,171 +14,157 @@
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.Price;
import org.prebid.server.bidder.model.Result;
-import org.prebid.server.currency.CurrencyConversionService;
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.resetdigital.ExtImpResetDigital;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.BidderUtil;
import org.prebid.server.util.HttpUtil;
-import java.math.BigDecimal;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.stream.Stream;
+import java.util.Optional;
public class ResetDigitalBidder implements Bidder {
private static final String DEFAULT_CURRENCY = "USD";
+ private static final TypeReference> EXT_TYPE_REFERENCE =
+ new TypeReference<>() {
+ };
private final String endpointUrl;
- private final CurrencyConversionService currencyConversionService;
private final JacksonMapper mapper;
- public ResetDigitalBidder(String endpointUrl,
- CurrencyConversionService currencyConversionService,
- JacksonMapper mapper) {
-
+ public ResetDigitalBidder(String endpointUrl, JacksonMapper mapper) {
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
- this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
this.mapper = Objects.requireNonNull(mapper);
}
@Override
public Result>> makeHttpRequests(BidRequest request) {
- final List bannerImps = new ArrayList<>();
- final List videoImps = new ArrayList<>();
- final List audioImps = new ArrayList<>();
- Price bidFloorPrice;
-
- for (Imp imp : request.getImp()) {
- try {
- bidFloorPrice = resolveBidFloor(imp, request);
- } catch (PreBidException e) {
- return Result.withError(BidderError.badInput(e.getMessage()));
- }
- populateBannerImps(bannerImps, bidFloorPrice, imp);
- populateVideoImps(videoImps, bidFloorPrice, imp);
- populateAudiImps(audioImps, bidFloorPrice, imp);
+ if (request.getImp().size() != 1) {
+ return Result.withError(BidderError.badInput(
+ "ResetDigital adapter supports only one impression per request"));
}
- return Result.withValues(getHttpRequests(request, bannerImps, videoImps, audioImps));
- }
-
- private List> getHttpRequests(BidRequest request,
- List bannerImps,
- List videoImps,
- List audioImps) {
-
- return Stream.of(bannerImps, videoImps, audioImps)
- .filter(CollectionUtils::isNotEmpty)
- .map(imp -> makeHttpRequest(request, imp))
- .toList();
- }
-
- private HttpRequest makeHttpRequest(BidRequest bidRequest, List imp) {
- final BidRequest outgoingRequest = bidRequest.toBuilder().imp(imp).build();
-
- return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper);
- }
-
- private static Imp modifyImp(Imp imp, Price bidFloorPrice) {
- return imp.toBuilder()
- .bidfloorcur(bidFloorPrice.getCurrency())
- .bidfloor(bidFloorPrice.getValue())
- .build();
- }
-
- private Price resolveBidFloor(Imp imp, BidRequest bidRequest) {
- final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
- return BidderUtil.isValidPrice(initialBidFloorPrice)
- ? convertBidFloor(initialBidFloorPrice, imp.getId(), bidRequest)
- : initialBidFloorPrice;
- }
-
- private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest) {
- final String bidFloorCur = bidFloorPrice.getCurrency();
+ final Imp imp = request.getImp().getFirst();
+ final ExtImpResetDigital extImp;
try {
- final BigDecimal convertedPrice = currencyConversionService
- .convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, DEFAULT_CURRENCY);
-
- return Price.of(DEFAULT_CURRENCY, convertedPrice);
+ extImp = parseImpExt(imp);
} catch (PreBidException e) {
- throw new PreBidException(
- "Unable to convert provided bid floor currency from %s to %s for imp `%s`"
- .formatted(bidFloorCur, DEFAULT_CURRENCY, impId));
+ return Result.withError(BidderError.badInput(e.getMessage()));
}
- }
- private static void populateBannerImps(List bannerImps, Price bidFloorPrice, Imp imp) {
- if (imp.getBanner() != null) {
- final Imp bannerImp = imp.toBuilder().video(null).xNative(null).audio(null).build();
- bannerImps.add(modifyImp(bannerImp, bidFloorPrice));
- }
+ final Imp modifiedImp = modifyImp(imp, extImp);
+ final BidRequest outgoingRequest = request.toBuilder()
+ .imp(Collections.singletonList(modifiedImp))
+ .build();
+
+ final String uri = endpointUrl + "?pid=" + HttpUtil.encodeUrl(extImp.getPlacementId());
+ final MultiMap headers = HttpUtil.headers()
+ .add(HttpUtil.X_OPENRTB_VERSION_HEADER, "2.5");
+
+ return Result.withValue(BidderUtil.defaultRequest(outgoingRequest, headers, uri, mapper));
}
- private static void populateVideoImps(List videoImps, Price bidFloorPrice, Imp imp) {
- if (imp.getVideo() != null) {
- final Imp videoImp = imp.toBuilder().banner(null).xNative(null).audio(null).build();
- videoImps.add(modifyImp(videoImp, bidFloorPrice));
+ private ExtImpResetDigital parseImpExt(Imp imp) {
+ try {
+ return mapper.mapper().convertValue(imp.getExt(), EXT_TYPE_REFERENCE).getBidder();
+ } catch (IllegalArgumentException e) {
+ throw new PreBidException("Error parsing resetDigitalExt from imp.ext: " + e.getMessage());
}
}
- private static void populateAudiImps(List audioImps, Price bidFloorPrice, Imp imp) {
- if (imp.getAudio() != null) {
- final Imp audioImp = imp.toBuilder().banner(null).xNative(null).video(null).build();
- audioImps.add(modifyImp(audioImp, bidFloorPrice));
- }
+ private static Imp modifyImp(Imp imp, ExtImpResetDigital extImp) {
+ return StringUtils.isBlank(imp.getTagid())
+ ? imp.toBuilder().tagid(extImp.getPlacementId()).build()
+ : imp;
}
@Override
public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) {
try {
+ final List errors = new ArrayList<>();
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
- return Result.withValues(extractBids(bidResponse, httpCall.getRequest().getPayload()));
+ return Result.of(extractBids(bidResponse, httpCall.getRequest().getPayload(), errors), errors);
} catch (DecodeException | PreBidException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}
- private static List extractBids(BidResponse bidResponse, BidRequest bidRequest) {
+ private static List extractBids(BidResponse bidResponse,
+ BidRequest bidRequest,
+ List errors) {
+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
return Collections.emptyList();
}
- if (bidResponse.getCur() != null && !StringUtils.equalsIgnoreCase(DEFAULT_CURRENCY, bidResponse.getCur())) {
- throw new PreBidException("Bidder support only USD currency");
+
+ final Imp imp = bidRequest.getImp().getFirst();
+ final String currency = StringUtils.isNotBlank(bidResponse.getCur())
+ ? bidResponse.getCur()
+ : bidRequest.getCur().stream().findFirst().orElse(DEFAULT_CURRENCY);
+
+ final List bidderBids = new ArrayList<>();
+ for (SeatBid seatBid : bidResponse.getSeatbid()) {
+ if (seatBid == null || CollectionUtils.isEmpty(seatBid.getBid())) {
+ continue;
+ }
+
+ for (Bid bid : seatBid.getBid()) {
+ try {
+ bidderBids.add(makeBidderBid(bid, seatBid.getSeat(), currency, imp));
+ } catch (PreBidException e) {
+ errors.add(BidderError.badServerResponse(e.getMessage()));
+ }
+ }
}
- return bidsFromResponse(bidResponse, bidRequest);
+
+ return bidderBids;
}
- private static List bidsFromResponse(BidResponse bidResponse, BidRequest bidRequest) {
- return bidResponse.getSeatbid().stream()
- .filter(Objects::nonNull)
- .map(SeatBid::getBid)
- .filter(Objects::nonNull)
- .flatMap(Collection::stream)
- .map(bid -> BidderBid.of(bid, getBidType(bid, bidRequest.getImp()), DEFAULT_CURRENCY))
- .toList();
+ private static BidderBid makeBidderBid(Bid bid, String seat, String currency, Imp imp) {
+ if (!BidderUtil.isValidPrice(bid.getPrice())) {
+ throw new PreBidException("price %s <= 0 filtered out".formatted(bid.getPrice()));
+ }
+
+ final BidType bidType = Optional.ofNullable(getBidType(bid))
+ .orElseGet(() -> getBidType(bid, imp));
+
+ return StringUtils.isNotBlank(seat)
+ ? BidderBid.of(bid, bidType, seat, currency)
+ : BidderBid.of(bid, bidType, currency);
}
- private static BidType getBidType(Bid bid, List imps) {
- final String impId = bid.getImpid();
- for (Imp imp : imps) {
- if (imp.getId().equals(impId)) {
- if (imp.getBanner() != null) {
- return BidType.banner;
- } else if (imp.getVideo() != null) {
- return BidType.video;
- } else if (imp.getAudio() != null) {
- return BidType.audio;
- }
- }
+ private static BidType getBidType(Bid bid) {
+ final Integer mtype = bid.getMtype();
+ return switch (mtype) {
+ case 1 -> BidType.banner;
+ case 2 -> BidType.video;
+ case 3 -> BidType.audio;
+ case 4 -> BidType.xNative;
+ case null -> null;
+ default -> throw new PreBidException("Unsupported MType: " + mtype);
+ };
+ }
+
+ private static BidType getBidType(Bid bid, Imp imp) {
+ if (!imp.getId().equals(bid.getImpid())) {
+ throw new PreBidException("No matching impression found for ImpID: " + bid.getImpid());
+ }
+
+ if (imp.getVideo() != null) {
+ return BidType.video;
+ } else if (imp.getAudio() != null) {
+ return BidType.audio;
+ } else if (imp.getXNative() != null) {
+ return BidType.xNative;
}
- throw new PreBidException("Failed to find banner/video/audio impression " + impId);
+ return BidType.banner;
}
}
diff --git a/src/main/java/org/prebid/server/bidder/trustx/TrustxBidder.java b/src/main/java/org/prebid/server/bidder/trustx/TrustxBidder.java
new file mode 100644
index 00000000000..a74a88b1e3e
--- /dev/null
+++ b/src/main/java/org/prebid/server/bidder/trustx/TrustxBidder.java
@@ -0,0 +1,223 @@
+package org.prebid.server.bidder.trustx;
+
+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.Device;
+import com.iab.openrtb.request.Imp;
+import com.iab.openrtb.request.Site;
+import com.iab.openrtb.response.Bid;
+import com.iab.openrtb.response.BidResponse;
+import com.iab.openrtb.response.SeatBid;
+import io.vertx.core.MultiMap;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.prebid.server.bidder.Bidder;
+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.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.trustx.ExtBidBidderTrustx;
+import org.prebid.server.proto.openrtb.ext.request.trustx.ExtBidTrustx;
+import org.prebid.server.proto.openrtb.ext.request.trustx.ExtImpTrustx;
+import org.prebid.server.proto.openrtb.ext.request.trustx.ExtImpTrustxData;
+import org.prebid.server.proto.openrtb.ext.request.trustx.ExtImpTrustxDataAdServer;
+import org.prebid.server.proto.openrtb.ext.response.BidType;
+import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
+import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta;
+import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
+import org.prebid.server.util.BidderUtil;
+import org.prebid.server.util.HttpUtil;
+import org.prebid.server.util.ObjectUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class TrustxBidder implements Bidder {
+
+ private static final TypeReference> TRUSTX_BID_EXT_TYPE_REFERENCE =
+ new TypeReference<>() {
+ };
+
+ private static final String OPENRTB_VERSION = "2.6";
+
+ private final String endpointUrl;
+ private final JacksonMapper mapper;
+
+ public TrustxBidder(String endpointUrl, JacksonMapper mapper) {
+ this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
+ this.mapper = Objects.requireNonNull(mapper);
+ }
+
+ @Override
+ public Result>> makeHttpRequests(BidRequest request) {
+ final List updatedImps = request.getImp().stream().map(this::modifyImp).toList();
+ final BidRequest updatedRequest = request.toBuilder().imp(updatedImps).build();
+ return Result.withValue(BidderUtil.defaultRequest(updatedRequest, makeHeaders(request), endpointUrl, mapper));
+ }
+
+ private Imp modifyImp(Imp imp) {
+ final ExtImpTrustx impExt = parseImpExt(imp);
+
+ return impExt != null
+ ? imp.toBuilder().ext(mapper.mapper().valueToTree(modifyImpExt(impExt))).build()
+ : imp;
+ }
+
+ private ExtImpTrustx parseImpExt(Imp imp) {
+ try {
+ return mapper.mapper().convertValue(imp.getExt(), ExtImpTrustx.class);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private ExtImpTrustx modifyImpExt(ExtImpTrustx impExt) {
+ final String adSlot = Optional.ofNullable(impExt.getData())
+ .map(ExtImpTrustxData::getAdServer)
+ .map(ExtImpTrustxDataAdServer::getAdSlot)
+ .filter(StringUtils::isNotEmpty)
+ .orElse(null);
+
+ return adSlot != null ? impExt.toBuilder().gpid(adSlot).build() : impExt;
+ }
+
+ private MultiMap makeHeaders(BidRequest request) {
+ final Site site = request.getSite();
+ final String referrer = ObjectUtil.getIfNotNull(site, Site::getRef);
+ final String domain = ObjectUtil.getIfNotNull(site, Site::getDomain);
+
+ final Device device = request.getDevice();
+ final String ip = StringUtils.firstNonEmpty(
+ device != null ? device.getIpv6() : null,
+ device != null ? device.getIp() : null);
+ final String userAgent = device != null ? device.getUa() : null;
+
+ final MultiMap headers = HttpUtil.headers();
+
+ headers.add(HttpUtil.X_OPENRTB_VERSION_HEADER, OPENRTB_VERSION);
+ HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, referrer);
+ HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.ORIGIN_HEADER, domain);
+ HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, ip);
+ HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, userAgent);
+
+ return headers;
+ }
+
+ @Override
+ public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) {
+ try {
+ final BidResponse bidResponse = decodeBodyToBidResponse(httpCall);
+ return bidsFromResponse(bidResponse);
+ } catch (PreBidException e) {
+ return Result.withError(BidderError.badServerResponse(e.getMessage()));
+ }
+ }
+
+ private BidResponse decodeBodyToBidResponse(BidderCall httpCall) {
+ try {
+ return mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
+ } catch (DecodeException e) {
+ throw new PreBidException("Failed to parse response as BidResponse: " + e.getMessage());
+ }
+ }
+
+ private Result> bidsFromResponse(BidResponse bidResponse) {
+ final List errors = new ArrayList<>();
+ final List bidderBids = Stream.ofNullable(bidResponse)
+ .map(BidResponse::getSeatbid)
+ .filter(Objects::nonNull)
+ .flatMap(Collection::stream)
+ .filter(Objects::nonNull)
+ .map(SeatBid::getBid)
+ .filter(Objects::nonNull)
+ .flatMap(Collection::stream)
+ .filter(Objects::nonNull)
+ .map(bid -> makeBidderBid(bid, errors))
+ .filter(Objects::nonNull)
+ .toList();
+
+ return Result.of(bidderBids, errors);
+ }
+
+ private BidderBid makeBidderBid(Bid bid, List errors) {
+ final BidType bidType;
+ try {
+ bidType = getBidType(bid);
+ } catch (PreBidException e) {
+ errors.add(BidderError.badInput(e.getMessage()));
+ return null;
+ }
+
+ return BidderBid.builder()
+ .bid(modifyBid(bid))
+ .type(bidType)
+ .videoInfo(bidType == BidType.video ? makeExtBidPrebidVideo(bid) : null)
+ .build();
+ }
+
+ private Bid modifyBid(Bid bid) {
+ final ExtPrebid ext = parseBidExt(bid.getExt());
+ if (ext == null) {
+ return bid;
+ }
+
+ final ExtBidBidderTrustx extBidder = ext.getBidder();
+ final String networkName = Optional.ofNullable(extBidder)
+ .map(ExtBidBidderTrustx::getTrustx)
+ .map(ExtBidTrustx::getNetworkName)
+ .filter(StringUtils::isNotEmpty)
+ .orElse(null);
+ if (networkName == null) {
+ return bid;
+ }
+
+ final ExtBidPrebid modifiedExtPrebid = Optional.ofNullable(ext.getPrebid())
+ .map(ExtBidPrebid::toBuilder)
+ .orElseGet(ExtBidPrebid::builder)
+ .meta(ExtBidPrebidMeta.builder().networkName(networkName).build())
+ .build();
+ final ObjectNode modifiedExt = mapper.mapper().valueToTree(ExtPrebid.of(modifiedExtPrebid, extBidder));
+
+ return bid.toBuilder().ext(modifiedExt).build();
+ }
+
+ private ExtPrebid parseBidExt(ObjectNode bidExt) {
+ try {
+ return mapper.mapper().convertValue(bidExt, TRUSTX_BID_EXT_TYPE_REFERENCE);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private static BidType getBidType(Bid bid) {
+ final Integer markupType = bid.getMtype();
+ if (markupType == null) {
+ throw new PreBidException("Missing MType for bid: " + bid.getId());
+ }
+
+ return switch (markupType) {
+ case 1 -> BidType.banner;
+ case 2 -> BidType.video;
+ default -> throw new PreBidException("Unsupported MType: %d".formatted(markupType));
+ };
+ }
+
+ private static ExtBidPrebidVideo makeExtBidPrebidVideo(Bid bid) {
+ final Integer dur = bid.getDur();
+ final List cat = bid.getCat();
+
+ return ExtBidPrebidVideo.of(
+ dur != null && dur > 0 ? dur : null,
+ CollectionUtils.isNotEmpty(cat) ? cat.getFirst() : null);
+ }
+}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtBidBidderTrustx.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtBidBidderTrustx.java
new file mode 100644
index 00000000000..b693f341c3b
--- /dev/null
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtBidBidderTrustx.java
@@ -0,0 +1,9 @@
+package org.prebid.server.proto.openrtb.ext.request.trustx;
+
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ExtBidBidderTrustx {
+
+ ExtBidTrustx trustx;
+}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtBidTrustx.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtBidTrustx.java
new file mode 100644
index 00000000000..36ed5f6803c
--- /dev/null
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtBidTrustx.java
@@ -0,0 +1,11 @@
+package org.prebid.server.proto.openrtb.ext.request.trustx;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ExtBidTrustx {
+
+ @JsonProperty("networkName")
+ String networkName;
+}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtImpTrustx.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtImpTrustx.java
new file mode 100644
index 00000000000..3b6a882ef52
--- /dev/null
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtImpTrustx.java
@@ -0,0 +1,19 @@
+package org.prebid.server.proto.openrtb.ext.request.trustx;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Builder;
+import lombok.Value;
+import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebid;
+
+@Value
+@Builder(toBuilder = true)
+public class ExtImpTrustx {
+
+ ExtImpPrebid prebid;
+
+ JsonNode bidder;
+
+ ExtImpTrustxData data;
+
+ String gpid;
+}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtImpTrustxData.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtImpTrustxData.java
new file mode 100644
index 00000000000..c89ec8f903a
--- /dev/null
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtImpTrustxData.java
@@ -0,0 +1,14 @@
+package org.prebid.server.proto.openrtb.ext.request.trustx;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ExtImpTrustxData {
+
+ @JsonProperty("pbadslot")
+ String pbAdSlot;
+
+ @JsonProperty("adserver")
+ ExtImpTrustxDataAdServer adServer;
+}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtImpTrustxDataAdServer.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtImpTrustxDataAdServer.java
new file mode 100644
index 00000000000..ea976ac01b9
--- /dev/null
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/trustx/ExtImpTrustxDataAdServer.java
@@ -0,0 +1,13 @@
+package org.prebid.server.proto.openrtb.ext.request.trustx;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ExtImpTrustxDataAdServer {
+
+ String name;
+
+ @JsonProperty("adslot")
+ String adSlot;
+}
diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ResetDigitalConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ResetDigitalConfiguration.java
index 4e4de161f66..33823866d5f 100644
--- a/src/main/java/org/prebid/server/spring/config/bidder/ResetDigitalConfiguration.java
+++ b/src/main/java/org/prebid/server/spring/config/bidder/ResetDigitalConfiguration.java
@@ -2,7 +2,6 @@
import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.resetdigital.ResetDigitalBidder;
-import org.prebid.server.currency.CurrencyConversionService;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
@@ -31,16 +30,12 @@ BidderConfigurationProperties configurationProperties() {
@Bean
BidderDeps resetDigitalBidderDeps(BidderConfigurationProperties resetDigitalConfigurationProperties,
@NotBlank @Value("${external-url}") String externalUrl,
- CurrencyConversionService currencyConversionService,
JacksonMapper mapper) {
return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(resetDigitalConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
- .bidderCreator(config -> new ResetDigitalBidder(
- config.getEndpoint(),
- currencyConversionService,
- mapper))
+ .bidderCreator(config -> new ResetDigitalBidder(config.getEndpoint(), mapper))
.assemble();
}
}
diff --git a/src/main/java/org/prebid/server/spring/config/bidder/TrustxConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/TrustxConfiguration.java
new file mode 100644
index 00000000000..35a7cb4a50b
--- /dev/null
+++ b/src/main/java/org/prebid/server/spring/config/bidder/TrustxConfiguration.java
@@ -0,0 +1,41 @@
+package org.prebid.server.spring.config.bidder;
+
+import org.prebid.server.bidder.BidderDeps;
+import org.prebid.server.bidder.trustx.TrustxBidder;
+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/trustx.yaml", factory = YamlPropertySourceFactory.class)
+public class TrustxConfiguration {
+
+ private static final String BIDDER_NAME = "trustx";
+
+ @Bean("trustxConfigurationProperties")
+ @ConfigurationProperties("adapters.trustx")
+ BidderConfigurationProperties configurationProperties() {
+ return new BidderConfigurationProperties();
+ }
+
+ @Bean
+ BidderDeps trustxBidderDeps(BidderConfigurationProperties trustxConfigurationProperties,
+ @NotBlank @Value("${external-url}") String externalUrl,
+ JacksonMapper mapper) {
+
+ return BidderDepsAssembler.forBidder(BIDDER_NAME)
+ .withConfig(trustxConfigurationProperties)
+ .usersyncerCreator(UsersyncerCreator.create(externalUrl))
+ .bidderCreator(config -> new TrustxBidder(config.getEndpoint(), mapper))
+ .assemble();
+ }
+}
diff --git a/src/main/resources/bidder-config/kargo.yaml b/src/main/resources/bidder-config/kargo.yaml
index 56d5e9ea22d..222cf206ecb 100644
--- a/src/main/resources/bidder-config/kargo.yaml
+++ b/src/main/resources/bidder-config/kargo.yaml
@@ -1,6 +1,6 @@
adapters:
kargo:
- endpoint: https://krk.kargo.com/api/v1/openrtb
+ endpoint: https://krk2.kargo.com/api/v1/openrtb
ortb-version: "2.6"
endpoint-compression: gzip
modifying-vast-xml-allowed: true
diff --git a/src/main/resources/bidder-config/oms.yaml b/src/main/resources/bidder-config/oms.yaml
index be8e9354c6a..68d535168f2 100644
--- a/src/main/resources/bidder-config/oms.yaml
+++ b/src/main/resources/bidder-config/oms.yaml
@@ -1,6 +1,6 @@
adapters:
oms:
- endpoint: http://rt.marphezis.com/pbs?pid={{PublisherId}}
+ endpoint: http://rt.marphezis.com/pbs?publisherId={{PublisherId}}
meta-info:
maintainer-email: prebid@onlinemediasolutions.com
app-media-types:
diff --git a/src/main/resources/bidder-config/resetdigital.yaml b/src/main/resources/bidder-config/resetdigital.yaml
index 885916d0d8d..a5843189dc0 100644
--- a/src/main/resources/bidder-config/resetdigital.yaml
+++ b/src/main/resources/bidder-config/resetdigital.yaml
@@ -1,21 +1,24 @@
adapters:
resetdigital:
- endpoint: http://b-us-east14.resetdigital.co:9001
+ endpoint: https://prebid.resetdigital.co
+ endpoint-compression: gzip
meta-info:
maintainer-email: biddersupport@resetdigital.co
app-media-types:
- banner
- video
+ - native
- audio
site-media-types:
- banner
- video
+ - native
- audio
supported-vendors:
vendor-id: 1162
usersync:
cookie-family-name: resetdigital
redirect:
- url: https://sync.resetdigital.co/csync?pid=rubicon&redir={{redirect_url}}
- support-cors: false
- uid-macro: '$USER_ID'
+ url: https://sync.resetdigital.co/usersync?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}
+ support-cors: true
+ uid-macro: '$UID'
diff --git a/src/main/resources/bidder-config/trustx.yaml b/src/main/resources/bidder-config/trustx.yaml
new file mode 100644
index 00000000000..6c41fb3b02d
--- /dev/null
+++ b/src/main/resources/bidder-config/trustx.yaml
@@ -0,0 +1,23 @@
+adapters:
+ trustx:
+ endpoint: https://ads.trustx.org/pbs
+ meta-info:
+ maintainer-email: prebid@trustx.org
+ app-media-types:
+ - banner
+ - video
+ site-media-types:
+ - banner
+ - video
+ supported-vendors:
+ vendor-id: 0
+ usersync:
+ cookie-family-name: trustx
+ iframe:
+ url: https://static.cdn.trustx.org/x/user_sync.html?source=pbs&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redirect={{redirect_url}}
+ support-cors: false
+ uid-macro: '$UID'
+ redirect:
+ url: https://sync.trustx.org/usync-pbs?us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redirect={{redirect_url}}
+ support-cors: false
+ uid-macro: '$UID'
diff --git a/src/main/resources/static/bidder-params/resetdigital.json b/src/main/resources/static/bidder-params/resetdigital.json
index 3710cfbc598..dc79816ce68 100644
--- a/src/main/resources/static/bidder-params/resetdigital.json
+++ b/src/main/resources/static/bidder-params/resetdigital.json
@@ -4,20 +4,12 @@
"description": "A schema which validates params accepted by the ResetDigital adapter",
"type": "object",
"properties": {
- "pubId": {
+ "placement_id": {
"type": "string",
- "description": "The publisher's ID provided"
- },
- "zoneId": {
- "type": "string",
- "description": "Zone ID"
- },
- "forceBid": {
- "type": "boolean",
- "description": "Force bids with a test creative"
+ "description": "Placement ID provided by ResetDigital",
+ "minLength": 1
}
},
- "required": [
- "pubId"
- ]
+ "required": ["placement_id"],
+ "additionalProperties": false
}
diff --git a/src/main/resources/static/bidder-params/trustx.json b/src/main/resources/static/bidder-params/trustx.json
new file mode 100644
index 00000000000..7560642aae6
--- /dev/null
+++ b/src/main/resources/static/bidder-params/trustx.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "TRUSTX Adapter Params",
+ "description": "A schema which validates params accepted by the TRUSTX adapter",
+ "type": "object",
+ "properties": {
+ "uid": {
+ "type": "integer",
+ "description": "The ad slot id"
+ },
+ "keywords": {
+ "type": "object",
+ "description": "Keywords"
+ }
+ }
+}
diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/Dependencies.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/Dependencies.groovy
index ab614e0ca5f..9c6407a4083 100644
--- a/src/test/groovy/org/prebid/server/functional/testcontainers/Dependencies.groovy
+++ b/src/test/groovy/org/prebid/server/functional/testcontainers/Dependencies.groovy
@@ -49,7 +49,7 @@ class Dependencies {
static void start() {
if (IS_LAUNCH_CONTAINERS) {
- localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack:s3-latest"))
+ localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack:s3-community-archive"))
.withNetwork(network)
.withServices(S3)
Startables.deepStart([networkServiceContainer, mysqlContainer, localStackContainer, influxdbContainer]).join()
diff --git a/src/test/java/org/prebid/server/bidder/mobkoi/MobkoiBidderTest.java b/src/test/java/org/prebid/server/bidder/mobkoi/MobkoiBidderTest.java
index 7d46ce2a0a8..027d6ceea69 100644
--- a/src/test/java/org/prebid/server/bidder/mobkoi/MobkoiBidderTest.java
+++ b/src/test/java/org/prebid/server/bidder/mobkoi/MobkoiBidderTest.java
@@ -91,10 +91,46 @@ public void makeHttpRequestsShouldAddPlacementIdOnlyInFirstImpressionTagId() {
assertThat(result.getValue())
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
.flatExtracting(BidRequest::getImp)
- .extracting(imp -> imp.getTagid())
+ .extracting(Imp::getTagid)
.containsExactly("pid", null);
}
+ @Test
+ public void makeHttpRequestsShouldOverrideTagIdWithPlacementId() {
+ // given
+ final ObjectNode mobkoiExt = impExt("pid");
+ final Imp givenImp = givenImp(impBuilder -> impBuilder.tagid("tagId").ext(mobkoiExt));
+ final BidRequest bidRequest = BidRequest.builder().imp(asList(givenImp)).build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getValue())
+ .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
+ .flatExtracting(BidRequest::getImp)
+ .extracting(Imp::getTagid)
+ .containsExactly("pid");
+ }
+
+ @Test
+ public void makeHttpRequestsShouldKeepOriginalTagIdWhenPlacementIdMissing() {
+ // given
+ final ObjectNode mobkoiExt = impExt(null);
+ final Imp givenImp = givenImp(impBuilder -> impBuilder.tagid("tagId").ext(mobkoiExt));
+ final BidRequest bidRequest = BidRequest.builder().imp(asList(givenImp)).build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getValue())
+ .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
+ .flatExtracting(BidRequest::getImp)
+ .extracting(Imp::getTagid)
+ .containsExactly("tagId");
+ }
+
@Test
public void makeHttpRequestsShouldOverrideUserExtAndSetConsent() {
// given
diff --git a/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java b/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java
index dbcc555489a..71b8c798266 100644
--- a/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java
+++ b/src/test/java/org/prebid/server/bidder/resetdigital/ResetDigitalBidderTest.java
@@ -10,11 +10,9 @@
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
+import io.vertx.core.MultiMap;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-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;
@@ -22,475 +20,377 @@
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.currency.CurrencyConversionService;
-import org.prebid.server.exception.PreBidException;
+import org.prebid.server.proto.openrtb.ext.response.BidType;
+import org.prebid.server.util.HttpUtil;
import java.math.BigDecimal;
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.AssertionsForClassTypes.tuple;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.BDDMockito.given;
-import static org.prebid.server.proto.openrtb.ext.response.BidType.audio;
-import static org.prebid.server.proto.openrtb.ext.response.BidType.banner;
-import static org.prebid.server.proto.openrtb.ext.response.BidType.video;
-
-@ExtendWith(MockitoExtension.class)
-public class ResetDigitalBidderTest extends VertxTest {
-
- public static final String ENDPOINT_URL = "https://test.endpoint.com";
+import static org.assertj.core.api.Assertions.tuple;
- @Mock
- private CurrencyConversionService currencyConversionService;
+public class ResetDigitalBidderTest extends VertxTest {
+ private static final String ENDPOINT_URL = "https://test.endpoint.com";
private ResetDigitalBidder target;
@BeforeEach
public void setUp() {
- target = new ResetDigitalBidder(ENDPOINT_URL, currencyConversionService, jacksonMapper);
+ target = new ResetDigitalBidder(ENDPOINT_URL, jacksonMapper);
}
@Test
public void creationShouldFailOnInvalidEndpointUrl() {
assertThatIllegalArgumentException().isThrownBy(() ->
- new ResetDigitalBidder("invalid_url", currencyConversionService, jacksonMapper));
+ new ResetDigitalBidder("invalid_url", jacksonMapper));
}
@Test
- public void makeHttpRequestShouldReturnEmptyResponseIfAbsentAnyTypeInImp() {
+ public void makeHttpRequestsShouldReturnErrorWhenNoImpressions() {
// given
- final BidRequest bidRequest = BidRequest.builder()
- .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null))))
- .build();
-
+ final BidRequest bidRequest = BidRequest.builder().imp(List.of()).build();
// when
final Result>> result = target.makeHttpRequests(bidRequest);
-
// then
- assertThat(result.getErrors()).hasSize(0);
- assertThat(result.getValue()).hasSize(0);
+ assertThat(result.getValue()).isEmpty();
+ assertThat(result.getErrors()).hasSize(1)
+ .extracting(BidderError::getMessage)
+ .containsExactly("ResetDigital adapter supports only one impression per request");
}
@Test
- public void makeHttpRequestShouldReturnEmptyResponseIfxNativeImpTypePresent() {
+ public void makeHttpRequestsShouldReturnErrorWhenMultipleImpressions() {
// given
final BidRequest bidRequest = BidRequest.builder()
- .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null)
- .xNative(Native.builder().build()))))
+ .imp(List.of(givenImp(identity()), givenImp(identity())))
.build();
-
// when
final Result>> result = target.makeHttpRequests(bidRequest);
-
// then
- assertThat(result.getErrors()).hasSize(0);
- assertThat(result.getValue()).hasSize(0);
+ assertThat(result.getValue()).isEmpty();
+ assertThat(result.getErrors()).hasSize(1)
+ .extracting(BidderError::getMessage)
+ .containsExactly("ResetDigital adapter supports only one impression per request");
}
@Test
- public void makeHttpRequestShouldReturnSeparateResponseWithBannerAndVideoAndAudioImp() {
+ public void makeHttpRequestsShouldReturnErrorWhenImpExtIsInvalid() {
// given
final BidRequest bidRequest = BidRequest.builder()
- .imp(singletonList(givenImp(impBuilder -> impBuilder
- .audio(Audio.builder().build())
- .video(Video.builder().build()))))
+ .imp(singletonList(Imp.builder()
+ .id("123")
+ .ext(jacksonMapper.mapper().createObjectNode().put("bidder", "invalid"))
+ .build()))
.build();
-
// when
final Result>> result = target.makeHttpRequests(bidRequest);
-
// then
- assertThat(result.getErrors()).hasSize(0);
- assertThat(result.getValue()).hasSize(3);
- assertThat(result.getValue().get(0))
- .extracting(HttpRequest::getPayload)
- .extracting(BidRequest::getImp)
- .extracting(a -> a.getFirst())
- .extracting(Imp::getBanner)
- .isNotNull();
-
- assertThat(result.getValue().get(1))
- .extracting(HttpRequest::getPayload)
- .extracting(BidRequest::getImp)
- .extracting(a -> a.getFirst())
- .extracting(Imp::getVideo)
- .isNotNull();
-
- assertThat(result.getValue().get(2))
- .extracting(HttpRequest::getPayload)
- .extracting(BidRequest::getImp)
- .extracting(a -> a.getFirst())
- .extracting(Imp::getAudio)
- .isNotNull();
+ assertThat(result.getValue()).isEmpty();
+ assertThat(result.getErrors()).hasSize(1)
+ .allSatisfy(error -> {
+ assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input);
+ assertThat(error.getMessage()).startsWith("Error parsing resetDigitalExt from imp.ext");
+ });
}
@Test
- public void makeHttpRequestShouldReturnSeparateResponseWithBannerAndVideoImp() {
+ public void makeHttpRequestsShouldCreateCorrectUrl() {
// given
- final BidRequest bidRequest = BidRequest.builder()
- .imp(singletonList(givenImp(impBuilder -> impBuilder
- .video(Video.builder().build()))))
- .build();
-
+ final BidRequest bidRequest = givenBidRequest(identity());
// when
final Result>> result = target.makeHttpRequests(bidRequest);
-
// then
- assertThat(result.getErrors()).hasSize(0);
- assertThat(result.getValue()).hasSize(2);
- assertThat(result.getValue().get(0))
- .extracting(HttpRequest::getPayload)
- .extracting(BidRequest::getImp)
- .extracting(a -> a.getFirst())
- .extracting(Imp::getBanner)
- .isNotNull();
-
- assertThat(result.getValue().get(1))
- .extracting(HttpRequest::getPayload)
- .extracting(BidRequest::getImp)
- .extracting(a -> a.getFirst())
- .extracting(Imp::getVideo)
- .isNotNull();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(HttpRequest::getUri)
+ .containsExactly("https://test.endpoint.com?pid=placementId123");
+ assertThat(result.getErrors()).isEmpty();
}
@Test
- public void makeHttpRequestShouldReturnSeparateResponseWithBannerAndAudioImp() {
+ public void makeHttpRequestsShouldContainXOpenRtbVersionHeader() {
// given
- final BidRequest bidRequest = BidRequest.builder()
- .imp(singletonList(givenImp(impBuilder -> impBuilder
- .audio(Audio.builder().build()))))
- .build();
-
+ final BidRequest bidRequest = givenBidRequest(identity());
// when
final Result>> result = target.makeHttpRequests(bidRequest);
-
// then
- assertThat(result.getErrors()).hasSize(0);
- assertThat(result.getValue()).hasSize(2);
- assertThat(result.getValue().get(0))
- .extracting(HttpRequest::getPayload)
- .extracting(BidRequest::getImp)
- .extracting(a -> a.getFirst())
- .extracting(Imp::getBanner)
- .isNotNull();
-
- assertThat(result.getValue().get(1))
- .extracting(HttpRequest::getPayload)
- .extracting(BidRequest::getImp)
- .extracting(a -> a.getFirst())
- .extracting(Imp::getAudio)
- .isNotNull();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getHeaders)
+ .flatExtracting(MultiMap::entries)
+ .extracting(Map.Entry::getKey, Map.Entry::getValue)
+ .containsOnlyOnce(tuple(HttpUtil.X_OPENRTB_VERSION_HEADER.toString(), "2.5"));
+ assertThat(result.getErrors()).isEmpty();
}
@Test
- public void makeHttpRequestShouldReturnSeparateResponseWithVideoAndAudioImp() {
+ public void makeHttpRequestsShouldSetTagIdFromPlacementIdWhenEmpty() {
// given
- final BidRequest bidRequest = BidRequest.builder()
- .imp(singletonList(givenImp(impBuilder -> impBuilder
- .banner(null)
- .video(Video.builder().build())
- .audio(Audio.builder().build()))))
- .build();
-
+ final BidRequest bidRequest = givenBidRequest(identity());
// when
final Result>> result = target.makeHttpRequests(bidRequest);
-
// then
- assertThat(result.getErrors()).hasSize(0);
- assertThat(result.getValue()).hasSize(2);
- assertThat(result.getValue().get(0))
- .extracting(HttpRequest::getPayload)
- .extracting(BidRequest::getImp)
- .extracting(a -> a.getFirst())
- .extracting(Imp::getVideo)
- .isNotNull();
-
- assertThat(result.getValue().get(1))
- .extracting(HttpRequest::getPayload)
- .extracting(BidRequest::getImp)
- .extracting(a -> a.getFirst())
- .extracting(Imp::getAudio)
- .isNotNull();
+ assertThat(result.getValue()).hasSize(1);
+ assertThat(result.getValue().getFirst().getPayload().getImp().getFirst().getTagid())
+ .isEqualTo("placementId123");
+ assertThat(result.getErrors()).isEmpty();
}
@Test
- public void makeHttpRequestShouldReturnResponseOnlyWithBannerImp() {
+ public void makeHttpRequestsShouldNotOverrideExistingTagId() {
// given
- final BidRequest bidRequest = BidRequest.builder()
- .imp(singletonList(givenImp(identity())))
- .build();
-
+ final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.tagid("existingTagId"));
// when
final Result>> result = target.makeHttpRequests(bidRequest);
-
// then
- assertThat(result.getErrors()).hasSize(0);
- assertThat(result.getValue())
- .extracting(HttpRequest::getPayload)
- .flatExtracting(BidRequest::getImp)
- .allSatisfy(imp -> {
- assertThat(imp.getBanner()).isNotNull();
- assertThat(imp.getVideo()).isNull();
- assertThat(imp.getAudio()).isNull();
- });
+ assertThat(result.getValue()).hasSize(1);
+ assertThat(result.getValue().getFirst().getPayload().getImp().getFirst().getTagid())
+ .isEqualTo("existingTagId");
+ assertThat(result.getErrors()).isEmpty();
}
@Test
- public void makeHttpRequestShouldReturnResponseOnlyWithVideoImp() {
+ public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
// given
- final BidRequest bidRequest = BidRequest.builder()
- .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null)
- .video(Video.builder().build()))))
- .build();
-
+ final BidderCall httpCall = givenHttpCall(givenBidRequest(identity()), "invalid");
// when
- final Result>> result = target.makeHttpRequests(bidRequest);
-
+ final Result> result = target.makeBids(httpCall, null);
// then
- assertThat(result.getErrors()).hasSize(0);
- assertThat(result.getValue())
- .extracting(HttpRequest::getPayload)
- .flatExtracting(BidRequest::getImp)
- .allSatisfy(imp -> {
- assertThat(imp.getBanner()).isNull();
- assertThat(imp.getVideo()).isNotNull();
- assertThat(imp.getAudio()).isNull();
+ assertThat(result.getErrors()).hasSize(1)
+ .allSatisfy(error -> {
+ assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response);
+ assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token");
});
+ assertThat(result.getValue()).isEmpty();
}
@Test
- public void makeHttpRequestShouldReturnResponseOnlyWithAudioImp() {
+ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException {
// given
- final BidRequest bidRequest = BidRequest.builder()
- .imp(singletonList(givenImp(impBuilder -> impBuilder.banner(null)
- .audio(Audio.builder().build()))))
- .build();
-
+ final BidderCall httpCall = givenHttpCall(
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(null));
// when
- final Result>> result = target.makeHttpRequests(bidRequest);
-
+ final Result> result = target.makeBids(httpCall, null);
// then
- assertThat(result.getErrors()).hasSize(0);
- assertThat(result.getValue())
- .extracting(HttpRequest::getPayload)
- .flatExtracting(BidRequest::getImp)
- .allSatisfy(imp -> {
- assertThat(imp.getBanner()).isNull();
- assertThat(imp.getVideo()).isNull();
- assertThat(imp.getAudio()).isNotNull();
- });
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).isEmpty();
}
@Test
- public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
+ public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException {
// given
- final BidderCall httpCall = givenHttpCall(null, "invalid");
-
+ final BidderCall httpCall = givenHttpCall(
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(BidResponse.builder().build()));
// when
final Result> result = target.makeBids(httpCall, null);
-
// then
- assertThat(result.getErrors()).hasSize(1)
- .allSatisfy(error -> {
- assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response);
- assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token");
- });
+ assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).isEmpty();
}
@Test
- public void makeHttpRequestsShouldConvertCurrencyIfRequestCurrencyDoesNotMatchBidderCurrency() {
+ public void makeBidsShouldReturnBannerBidByMType() throws JsonProcessingException {
// given
- given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString()))
- .willReturn(BigDecimal.TEN);
-
- final BidRequest bidRequest = givenBidRequest(
- impBuilder -> impBuilder.bidfloor(BigDecimal.ONE).bidfloorcur("EUR"));
-
+ final BidderCall httpCall = givenHttpCall(
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(
+ givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(1))));
// when
- final Result>> result = target.makeHttpRequests(bidRequest);
-
+ final Result> result = target.makeBids(httpCall, null);
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .extracting(HttpRequest::getPayload)
- .flatExtracting(BidRequest::getImp)
- .extracting(Imp::getBidfloor, Imp::getBidfloorcur)
- .containsOnly(tuple(BigDecimal.TEN, "USD"));
+ .containsExactly(BidderBid.of(Bid.builder().impid("123").mtype(1)
+ .price(BigDecimal.ONE).build(), BidType.banner, "USD"));
}
@Test
- public void makeHttpRequestsShouldReturnErrorMessageOnFailedCurrencyConversion() {
+ public void makeBidsShouldReturnVideoBidByMType() throws JsonProcessingException {
// given
- given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString()))
- .willThrow(PreBidException.class);
-
- final BidRequest bidRequest = givenBidRequest(
- impCustomizer -> impCustomizer.bidfloor(BigDecimal.ONE).bidfloorcur("EUR"));
-
+ final BidderCall httpCall = givenHttpCall(
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(
+ givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(2))));
// when
- final Result>> result = target.makeHttpRequests(bidRequest);
-
+ final Result> result = target.makeBids(httpCall, null);
// then
- assertThat(result.getErrors()).allSatisfy(bidderError -> {
- assertThat(bidderError.getType())
- .isEqualTo(BidderError.Type.bad_input);
- assertThat(bidderError.getMessage())
- .isEqualTo("Unable to convert provided bid floor currency from EUR to USD for imp `123`");
- });
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .containsExactly(BidderBid.of(Bid.builder().impid("123").mtype(2)
+ .price(BigDecimal.ONE).build(), BidType.video, "USD"));
}
@Test
- public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException {
+ public void makeBidsShouldReturnNativeBidByMType() throws JsonProcessingException {
// given
- final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null));
-
+ final BidderCall httpCall = givenHttpCall(
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(
+ givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(4))));
// when
final Result> result = target.makeBids(httpCall, null);
-
// then
assertThat(result.getErrors()).isEmpty();
- assertThat(result.getValue()).isEmpty();
+ assertThat(result.getValue())
+ .containsExactly(BidderBid.of(Bid.builder().impid("123").mtype(4)
+ .price(BigDecimal.ONE).build(), BidType.xNative, "USD"));
}
@Test
- public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException {
+ public void makeBidsShouldReturnAudioBidByMType() throws JsonProcessingException {
// given
- final BidderCall httpCall = givenHttpCall(null,
- mapper.writeValueAsString(BidResponse.builder().build()));
-
+ final BidderCall httpCall = givenHttpCall(
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(
+ givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(3))));
// when
final Result> result = target.makeBids(httpCall, null);
-
// then
assertThat(result.getErrors()).isEmpty();
- assertThat(result.getValue()).isEmpty();
+ assertThat(result.getValue())
+ .containsExactly(BidderBid.of(Bid.builder().impid("123").mtype(3)
+ .price(BigDecimal.ONE).build(), BidType.audio, "USD"));
}
@Test
- public void makeBidsShouldReturnBannerBidIfBannerIsPresentInRequestImp() throws JsonProcessingException {
+ public void makeBidsShouldReturnBannerBidFromImpWhenMTypeIsNull() throws JsonProcessingException {
// given
final BidderCall httpCall = givenHttpCall(
- BidRequest.builder()
- .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build()))
- .build(),
- mapper.writeValueAsString(
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(
givenBidResponse(bidBuilder -> bidBuilder.impid("123"))));
-
// when
final Result> result = target.makeBids(httpCall, null);
-
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD"));
+ .containsExactly(BidderBid.of(Bid.builder().impid("123")
+ .price(BigDecimal.ONE).build(), BidType.banner, "USD"));
}
@Test
- public void makeBidsShouldReturnVideoBidIfVideoIsPresentInRequestImp() throws JsonProcessingException {
+ public void makeBidsShouldReturnVideoBidFromImpWhenMTypeIsNull() throws JsonProcessingException {
// given
final BidderCall httpCall = givenHttpCall(
- BidRequest.builder()
- .imp(singletonList(Imp.builder().id("123").video(Video.builder().build()).build()))
- .build(),
- mapper.writeValueAsString(
+ givenBidRequest(impBuilder -> impBuilder.banner(null).video(Video.builder().build())),
+ jacksonMapper.mapper().writeValueAsString(
givenBidResponse(bidBuilder -> bidBuilder.impid("123"))));
-
// when
final Result> result = target.makeBids(httpCall, null);
-
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), video, "USD"));
+ .containsExactly(BidderBid.of(Bid.builder().impid("123")
+ .price(BigDecimal.ONE).build(), BidType.video, "USD"));
}
@Test
- public void makeBidsShouldReturnAudioBidIfAudioIsPresentInRequestImp() throws JsonProcessingException {
+ public void makeBidsShouldReturnAudioBidFromImpWhenMTypeIsNull() throws JsonProcessingException {
// given
final BidderCall httpCall = givenHttpCall(
- BidRequest.builder()
- .imp(singletonList(Imp.builder().id("123").audio(Audio.builder().build()).build()))
- .build(),
- mapper.writeValueAsString(
+ givenBidRequest(impBuilder -> impBuilder.banner(null).audio(Audio.builder().build())),
+ jacksonMapper.mapper().writeValueAsString(
givenBidResponse(bidBuilder -> bidBuilder.impid("123"))));
-
// when
final Result> result = target.makeBids(httpCall, null);
-
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
- .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), audio, "USD"));
+ .containsExactly(BidderBid.of(Bid.builder().impid("123")
+ .price(BigDecimal.ONE).build(), BidType.audio, "USD"));
}
@Test
- public void makeBidsShouldReturnErrorBidIfBidTypeIsAbsentInRequestImp() throws JsonProcessingException {
+ public void makeBidsShouldReturnNativeBidFromImpWhenMTypeIsNull() throws JsonProcessingException {
// given
final BidderCall httpCall = givenHttpCall(
- BidRequest.builder()
- .imp(singletonList(Imp.builder().id("123").build()))
- .build(),
- mapper.writeValueAsString(
+ givenBidRequest(impBuilder -> impBuilder.banner(null).xNative(Native.builder().build())),
+ jacksonMapper.mapper().writeValueAsString(
givenBidResponse(bidBuilder -> bidBuilder.impid("123"))));
-
// when
final Result> result = target.makeBids(httpCall, null);
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .containsExactly(BidderBid.of(Bid.builder().impid("123")
+ .price(BigDecimal.ONE).build(), BidType.xNative, "USD"));
+ }
+ @Test
+ public void makeBidsShouldFilterOutZeroPriceBids() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(
+ givenBidResponse(bidBuilder -> bidBuilder.impid("123").price(BigDecimal.ZERO))));
+ // when
+ final Result> result = target.makeBids(httpCall, null);
// then
+ assertThat(result.getValue()).isEmpty();
assertThat(result.getErrors()).hasSize(1)
.extracting(BidderError::getMessage)
- .containsExactly("Failed to find banner/video/audio impression 123");
- assertThat(result.getValue()).isEmpty();
+ .containsExactly("price 0 <= 0 filtered out");
}
@Test
- public void makeBidsShouldReturnErrorIfBidCurIsNotUsd() throws JsonProcessingException {
+ public void makeBidsShouldReturnErrorWhenMTypeIsUnsupported() throws JsonProcessingException {
// given
final BidderCall httpCall = givenHttpCall(
- BidRequest.builder()
- .imp(singletonList(Imp.builder().id("123").build()))
- .build(),
- mapper.writeValueAsString(givenBidResponse(identity()).toBuilder().cur("EUR").build()));
-
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(
+ givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(99))));
// when
final Result> result = target.makeBids(httpCall, null);
-
// then
+ assertThat(result.getValue()).isEmpty();
assertThat(result.getErrors()).hasSize(1)
.extracting(BidderError::getMessage)
- .containsExactly("Bidder support only USD currency");
- assertThat(result.getValue()).isEmpty();
+ .containsExactly("Unsupported MType: 99");
}
- private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer,
- UnaryOperator impCustomizer) {
-
- return bidRequestCustomizer.apply(BidRequest.builder()
- .imp(singletonList(givenImp(impCustomizer))))
- .build();
+ @Test
+ public void makeBidsShouldUseCurrencyFromBidResponse() throws JsonProcessingException {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ givenBidRequest(identity()),
+ jacksonMapper.mapper().writeValueAsString(
+ givenBidResponse(identity()).toBuilder().cur("EUR").build()));
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBidCurrency)
+ .containsExactly("EUR");
}
- private static BidRequest givenBidRequest(UnaryOperator impCustomizer) {
- return givenBidRequest(identity(), impCustomizer);
+ private BidRequest givenBidRequest(UnaryOperator impCustomizer) {
+ return BidRequest.builder()
+ .imp(singletonList(givenImp(impCustomizer)))
+ .build();
}
- private static Imp givenImp(UnaryOperator impCustomizer) {
+ private Imp givenImp(UnaryOperator impCustomizer) {
return impCustomizer.apply(Imp.builder()
.id("123")
- .banner(Banner.builder().w(23).h(25).build()))
+ .banner(Banner.builder().w(300).h(250).build())
+ .ext(jacksonMapper.mapper().createObjectNode()
+ .set("bidder", jacksonMapper.mapper().createObjectNode()
+ .put("placement_id", "placementId123"))))
.build();
}
private static BidResponse givenBidResponse(UnaryOperator bidCustomizer) {
return BidResponse.builder()
- .seatbid(singletonList(SeatBid.builder().bid(singletonList(bidCustomizer.apply(Bid.builder()).build()))
+ .seatbid(singletonList(SeatBid.builder()
+ .bid(singletonList(bidCustomizer.apply(Bid.builder()
+ .impid("123")
+ .price(BigDecimal.ONE)).build()))
.build()))
.cur("USD")
.build();
diff --git a/src/test/java/org/prebid/server/bidder/trustx/TrustxBidderTest.java b/src/test/java/org/prebid/server/bidder/trustx/TrustxBidderTest.java
new file mode 100644
index 00000000000..f8b8d3bd9ea
--- /dev/null
+++ b/src/test/java/org/prebid/server/bidder/trustx/TrustxBidderTest.java
@@ -0,0 +1,754 @@
+package org.prebid.server.bidder.trustx;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+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.response.Bid;
+import com.iab.openrtb.response.BidResponse;
+import com.iab.openrtb.response.SeatBid;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.vertx.core.MultiMap;
+import io.vertx.core.http.HttpMethod;
+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.proto.openrtb.ext.request.trustx.ExtBidBidderTrustx;
+import org.prebid.server.proto.openrtb.ext.request.trustx.ExtBidTrustx;
+import org.prebid.server.proto.openrtb.ext.request.trustx.ExtImpTrustx;
+import org.prebid.server.proto.openrtb.ext.request.trustx.ExtImpTrustxData;
+import org.prebid.server.proto.openrtb.ext.request.trustx.ExtImpTrustxDataAdServer;
+import org.prebid.server.proto.openrtb.ext.response.BidType;
+import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
+import org.prebid.server.util.HttpUtil;
+
+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;
+
+public class TrustxBidderTest extends VertxTest {
+
+ private static final String ENDPOINT_URL = "https://test.endpoint.com/";
+
+ private final TrustxBidder target = new TrustxBidder(ENDPOINT_URL, jacksonMapper);
+
+ @Test
+ public void creationShouldFailOnInvalidEndpointUrl() {
+ assertThatIllegalArgumentException().isThrownBy(() -> new TrustxBidder("invalid_url", jacksonMapper));
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnExpectedRequestUrl() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(givenImp());
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getUri)
+ .containsExactly(ENDPOINT_URL);
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnExpectedRequestMethod() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(givenImp());
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getMethod)
+ .containsExactly(HttpMethod.POST);
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnASingleRequest() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ givenImp("imp1", givenImpExt()),
+ givenImp("imp2", givenImpExt()));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1);
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnExpectedDefaultHeaders() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(givenImp());
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getHeaders)
+ .flatExtracting(MultiMap::entries)
+ .extracting(Map.Entry::getKey, Map.Entry::getValue)
+ .containsExactlyInAnyOrder(
+ tuple(HttpUtil.CONTENT_TYPE_HEADER.toString(), HttpUtil.APPLICATION_JSON_CONTENT_TYPE),
+ tuple(HttpUtil.ACCEPT_HEADER.toString(), HttpHeaderValues.APPLICATION_JSON.toString()),
+ tuple(HttpUtil.X_OPENRTB_VERSION_HEADER.toString(), "2.6"));
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnExpectedHeadersWhenSiteIsPresent() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ request -> request.site(Site.builder()
+ .ref("https://referer.com")
+ .domain("example.com")
+ .build()),
+ givenImp());
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getHeaders)
+ .flatExtracting(MultiMap::entries)
+ .extracting(Map.Entry::getKey, Map.Entry::getValue)
+ .contains(
+ tuple(HttpUtil.REFERER_HEADER.toString(), "https://referer.com"),
+ tuple(HttpUtil.ORIGIN_HEADER.toString(), "example.com"));
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnExpectedHeadersWhenDeviceIsPresent() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ request -> request.device(Device.builder()
+ .ip("192.168.1.1")
+ .ua("Mozilla/5.0")
+ .build()),
+ givenImp());
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getHeaders)
+ .flatExtracting(MultiMap::entries)
+ .extracting(Map.Entry::getKey, Map.Entry::getValue)
+ .contains(
+ tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "192.168.1.1"),
+ tuple(HttpUtil.USER_AGENT_HEADER.toString(), "Mozilla/5.0"));
+ }
+
+ @Test
+ public void makeHttpRequestsShouldPreferIpv6InXForwardedForHeader() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ request -> request.device(Device.builder()
+ .ip("192.168.1.1")
+ .ipv6("2001:db8::1").build()),
+ givenImp());
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getHeaders)
+ .flatExtracting(headers -> headers.getAll(HttpUtil.X_FORWARDED_FOR_HEADER.toString()))
+ .containsExactly("2001:db8::1");
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnImpWithUnmodifiedExtWhenExtIsInvalid() {
+ // given
+ final ObjectNode invalidExt = mapper.valueToTree(Map.of("data", "invalid"));
+ final BidRequest bidRequest = givenBidRequest(givenImp("imp1", invalidExt));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getPayload)
+ .flatExtracting(BidRequest::getImp)
+ .extracting(Imp::getExt)
+ .containsExactly(invalidExt);
+ }
+
+ @Test
+ public void makeHttpRequestsShouldReturnImpWithUnmodifiedExtWhenExtIsNull() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(givenImp("imp1", null));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getPayload)
+ .flatExtracting(BidRequest::getImp)
+ .extracting(Imp::getExt)
+ .hasSize(1)
+ .containsOnlyNulls();
+ }
+
+ @Test
+ public void makeHttpRequestsShouldSetGpidFromAdSlotWhenAdSlotIsPresent() {
+ // given
+ final ObjectNode impExt = givenImpExtWithAdSlot("adSlotValue", "gpidValue");
+ final BidRequest bidRequest = givenBidRequest(givenImp("imp1", impExt));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getPayload)
+ .flatExtracting(BidRequest::getImp)
+ .extracting(Imp::getExt)
+ .extracting(ext -> ext.get("gpid").textValue())
+ .containsExactly("adSlotValue");
+ }
+
+ @Test
+ public void makeHttpRequestsShouldPreserveExistingGpidWhenAdSlotIsAbsent() {
+ // given
+ final ObjectNode impExt = givenImpExtWithAdSlot(null, "existingGpid");
+ final BidRequest bidRequest = givenBidRequest(givenImp("imp1", impExt));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getPayload)
+ .flatExtracting(BidRequest::getImp)
+ .extracting(Imp::getExt)
+ .extracting(ext -> ext.get("gpid").textValue())
+ .containsExactly("existingGpid");
+ }
+
+ @Test
+ public void makeHttpRequestsShouldHandleMultipleImpressions() {
+ // given
+ final BidRequest bidRequest = givenBidRequest(
+ givenImp("imp1", givenImpExtWithAdSlot("adSlot1", "gpid1")),
+ givenImp("imp2", givenImpExtWithAdSlot("adSlot2", "gpid2")));
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getPayload)
+ .flatExtracting(BidRequest::getImp)
+ .hasSize(2)
+ .extracting(imp -> imp.getExt().get("gpid").textValue())
+ .containsExactly("adSlot1", "adSlot2");
+ }
+
+ @Test
+ public void makeBidsShouldReturnErrorWhenResponseBodyIsInvalid() {
+ // given
+ final BidderCall httpCall = BidderCall.succeededHttp(
+ HttpRequest.builder().payload(null).build(),
+ HttpResponse.of(200, null, "\"Invalid body\""),
+ null);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> {
+ assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response);
+ assertThat(error.getMessage()).startsWith("Failed to parse response as BidResponse: ");
+ });
+ assertThat(result.getValue()).isEmpty();
+ }
+
+ @Test
+ public void makeBidsShouldReturnErrorWhenMTypeIsNull() {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ Bid.builder().id("bidId").mtype(null).build());
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).containsExactly(BidderError.badInput("Missing MType for bid: bidId"));
+ assertThat(result.getValue()).isEmpty();
+ }
+
+ @Test
+ public void makeBidsShouldReturnErrorForUnsupportedMType() {
+ // given
+ final BidderCall httpCall = givenHttpCall(
+ Bid.builder().id("bidId").mtype(3).build());
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).containsExactly(BidderError.badInput("Unsupported MType: 3"));
+ assertThat(result.getValue()).isEmpty();
+ }
+
+ @Test
+ public void makeBidsShouldReturnBannerBidWhenMTypeIs1() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(1).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getType)
+ .containsExactly(BidType.banner);
+ }
+
+ @Test
+ public void makeBidsShouldReturnVideoBidWhenMTypeIs2() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(2).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getType)
+ .containsExactly(BidType.video);
+ }
+
+ @Test
+ public void makeBidsShouldSetVideoInfoDurationWhenBidDurIsPositive() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(2).dur(30).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getVideoInfo)
+ .extracting(ExtBidPrebidVideo::getDuration)
+ .containsExactly(30);
+ }
+
+ @Test
+ public void makeBidsShouldNotSetVideoInfoDurationWhenBidDurIsZero() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(2).dur(0).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getVideoInfo)
+ .extracting(ExtBidPrebidVideo::getDuration)
+ .hasSize(1)
+ .containsOnlyNulls();
+ }
+
+ @Test
+ public void makeBidsShouldNotSetVideoInfoDurationWhenBidDurIsNull() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(2).dur(null).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getVideoInfo)
+ .extracting(ExtBidPrebidVideo::getDuration)
+ .hasSize(1)
+ .containsOnlyNulls();
+ }
+
+ @Test
+ public void makeBidsShouldSetVideoInfoCategoryWhenCatListIsNotEmpty() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(2).cat(List.of("IAB1")).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getVideoInfo)
+ .extracting(ExtBidPrebidVideo::getPrimaryCategory)
+ .containsExactly("IAB1");
+ }
+
+ @Test
+ public void makeBidsShouldSetFirstCategoryWhenMultipleCategoriesExist() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(2).cat(List.of("IAB1", "IAB2", "IAB3")).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getVideoInfo)
+ .extracting(ExtBidPrebidVideo::getPrimaryCategory)
+ .containsExactly("IAB1");
+ }
+
+ @Test
+ public void makeBidsShouldNotSetVideoInfoCategoryWhenCatListIsEmpty() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(2).cat(List.of()).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getVideoInfo)
+ .extracting(ExtBidPrebidVideo::getPrimaryCategory)
+ .hasSize(1)
+ .containsOnlyNulls();
+ }
+
+ @Test
+ public void makeBidsShouldNotSetVideoInfoCategoryWhenCatListIsNull() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(2).cat(null).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getVideoInfo)
+ .extracting(ExtBidPrebidVideo::getPrimaryCategory)
+ .hasSize(1)
+ .containsOnlyNulls();
+ }
+
+ @Test
+ public void makeBidsShouldNotSetVideoInfoForBannerBid() {
+ // given
+ final Bid bid = Bid.builder().id("bidId").mtype(1).dur(30).cat(List.of("IAB1")).build();
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getVideoInfo)
+ .hasSize(1)
+ .containsOnlyNulls();
+ }
+
+ @Test
+ public void makeBidsShouldModifyBidExtWithNetworkNameFromTrustxExtension() {
+ // given
+ final ObjectNode bidExt = givenBidExtWithNetworkName("testNetwork");
+ final Bid bid = givenBidWithExt(bidExt);
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getExt)
+ .extracting(ext -> ext.path("prebid").path("meta").path("networkName").textValue())
+ .containsExactly("testNetwork");
+ }
+
+ @Test
+ public void makeBidsShouldNotModifyBidExtWhenNetworkNameIsEmpty() {
+ // given
+ final ObjectNode bidExt = givenBidExtWithNetworkName("");
+ final Bid bid = givenBidWithExt(bidExt);
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getExt)
+ .extracting(ext -> ext.path("prebid").path("meta").has("networkName"))
+ .containsExactly(false);
+ }
+
+ @Test
+ public void makeBidsShouldNotModifyBidExtWhenNetworkNameIsNull() {
+ // given
+ final ObjectNode bidExt = givenBidExtWithNetworkName(null);
+ final Bid bid = givenBidWithExt(bidExt);
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getExt)
+ .extracting(ext -> ext.path("prebid").path("meta").has("networkName"))
+ .containsExactly(false);
+ }
+
+ @Test
+ public void makeBidsShouldNotModifyBidExtWhenBidExtIsInvalid() {
+ // given
+ final ObjectNode bidExt = mapper.createObjectNode().put("bidder", "invalid");
+ final Bid bid = givenBidWithExt(bidExt);
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getExt)
+ .containsExactly(bidExt);
+ }
+
+ @Test
+ public void makeBidsShouldNotModifyBidExtWhenBidExtIsNull() {
+ // given
+ final Bid bid = givenBidWithExt(null);
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getExt)
+ .hasSize(1)
+ .containsOnlyNulls();
+ }
+
+ @Test
+ public void makeBidsShouldPreserveExistingPrebidExtWhenModifyingMeta() {
+ // given
+ final ObjectNode prebidNode = mapper.createObjectNode().put("type", "banner");
+ final ObjectNode bidExt = givenBidExtWithNetworkName("testNetwork");
+ bidExt.set("prebid", prebidNode);
+ final Bid bid = givenBidWithExt(bidExt);
+ final BidderCall httpCall = givenHttpCall(bid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue())
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getExt)
+ .hasSize(1)
+ .allSatisfy(ext -> {
+ assertThat(ext.path("prebid").path("type").textValue()).isEqualTo("banner");
+ assertThat(ext.path("prebid").path("meta").path("networkName").textValue())
+ .isEqualTo("testNetwork");
+ });
+ }
+
+ @Test
+ public void makeBidsShouldReturnMultipleBidsFromSingleSeatBid() {
+ // given
+ final Bid bid1 = Bid.builder().id("bidId1").mtype(1).build();
+ final Bid bid2 = Bid.builder().id("bidId2").mtype(2).build();
+ final BidderCall httpCall = givenHttpCall(bid1, bid2);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(2)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getId)
+ .containsExactly("bidId1", "bidId2");
+ }
+
+ @Test
+ public void makeBidsShouldReturnBidsFromMultipleSeatBids() {
+ // given
+ final Bid bid1 = Bid.builder().id("bidId1").mtype(1).build();
+ final Bid bid2 = Bid.builder().id("bidId2").mtype(2).build();
+ final BidderCall httpCall = givenHttpCallWithMultipleSeatBids(
+ SeatBid.builder().bid(List.of(bid1)).build(),
+ SeatBid.builder().bid(List.of(bid2)).build());
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(2)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getId)
+ .containsExactly("bidId1", "bidId2");
+ }
+
+ @Test
+ public void makeBidsShouldReturnValidBidsAndErrorsWhenMixedInput() {
+ // given
+ final Bid validBid = Bid.builder().id("validBidId").mtype(1).build();
+ final Bid invalidBid = Bid.builder().id("invalidBidId").mtype(null).build();
+ final BidderCall httpCall = givenHttpCall(validBid, invalidBid);
+
+ // when
+ final Result> result = target.makeBids(httpCall, null);
+
+ // then
+ assertThat(result.getErrors()).containsExactly(BidderError.badInput("Missing MType for bid: invalidBidId"));
+ assertThat(result.getValue()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getId)
+ .containsExactly("validBidId");
+ }
+
+ private static BidRequest givenBidRequest(Imp... imps) {
+ return givenBidRequest(identity(), imps);
+ }
+
+ private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer,
+ Imp... imps) {
+
+ return bidRequestCustomizer
+ .apply(BidRequest.builder()
+ .id("testBidRequestId")
+ .imp(List.of(imps)))
+ .build();
+ }
+
+ private static Imp givenImp() {
+ return givenImp(givenImpExt());
+ }
+
+ private static Imp givenImp(ObjectNode impExt) {
+ return givenImp(null, impExt);
+ }
+
+ private static Imp givenImp(String impId, ObjectNode impExt) {
+ return Imp.builder()
+ .id(impId)
+ .ext(impExt)
+ .build();
+ }
+
+ private static ObjectNode givenImpExt() {
+ return mapper.valueToTree(ExtImpTrustx.builder().build());
+ }
+
+ private static ObjectNode givenImpExtWithAdSlot(String adSlot, String gpid) {
+ return mapper.valueToTree(ExtImpTrustx.builder()
+ .data(ExtImpTrustxData.of(null, ExtImpTrustxDataAdServer.of(null, adSlot)))
+ .gpid(gpid)
+ .build());
+ }
+
+ private static Bid givenBidWithExt(ObjectNode bidExt) {
+ return Bid.builder().id("bidId").mtype(1).ext(bidExt).build();
+ }
+
+ private static ObjectNode givenBidExtWithNetworkName(String networkName) {
+ return mapper.valueToTree(Map.of("bidder", ExtBidBidderTrustx.of(ExtBidTrustx.of(networkName))));
+ }
+
+ private static BidderCall givenHttpCall(Bid... bids) {
+ return BidderCall.succeededHttp(
+ HttpRequest.builder().payload(null).build(),
+ HttpResponse.of(HttpResponseStatus.OK.code(), null, givenBidResponse(bids)),
+ null);
+ }
+
+ private static BidderCall givenHttpCallWithMultipleSeatBids(SeatBid... seatBids) {
+ return BidderCall.succeededHttp(
+ HttpRequest.builder().payload(null).build(),
+ HttpResponse.of(HttpResponseStatus.OK.code(), null, givenBidResponseWithSeatBids(seatBids)),
+ null);
+ }
+
+ private static String givenBidResponse(Bid... bids) {
+ try {
+ return mapper.writeValueAsString(BidResponse.builder()
+ .seatbid(singletonList(SeatBid.builder().bid(List.of(bids)).build()))
+ .build());
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException("Error encoding BidResponse to json: " + e);
+ }
+ }
+
+ private static String givenBidResponseWithSeatBids(SeatBid... seatBids) {
+ try {
+ return mapper.writeValueAsString(BidResponse.builder()
+ .seatbid(List.of(seatBids))
+ .build());
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException("Error encoding BidResponse to json: " + e);
+ }
+ }
+}
diff --git a/src/test/java/org/prebid/server/it/ResetDigitalTest.java b/src/test/java/org/prebid/server/it/ResetDigitalTest.java
index 48138dfccfe..5264c29fc7e 100644
--- a/src/test/java/org/prebid/server/it/ResetDigitalTest.java
+++ b/src/test/java/org/prebid/server/it/ResetDigitalTest.java
@@ -8,6 +8,7 @@
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;
@@ -19,6 +20,7 @@ public class ResetDigitalTest extends IntegrationTest {
public void openrtb2AuctionShouldRespondWithBidsFromResetDigital() throws IOException, JSONException {
// given
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/resetdigital-exchange"))
+ .withQueryParam("pid", equalTo("publisherTestID"))
.withRequestBody(equalToJson(jsonFrom("openrtb2/resetdigital/test-resetdigital-bid-request.json")))
.willReturn(aResponse().withBody(
jsonFrom("openrtb2/resetdigital/test-resetdigital-bid-response.json"))));
diff --git a/src/test/java/org/prebid/server/it/TrustxTest.java b/src/test/java/org/prebid/server/it/TrustxTest.java
new file mode 100644
index 00000000000..966e1f553ef
--- /dev/null
+++ b/src/test/java/org/prebid/server/it/TrustxTest.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 TrustxTest extends IntegrationTest {
+
+ @Test
+ public void openrtb2AuctionShouldRespondWithBidsFromTrustx() throws IOException, JSONException {
+ // given
+ WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/trustx-exchange"))
+ .withRequestBody(equalToJson(jsonFrom("openrtb2/trustx/test-trustx-bid-request.json")))
+ .willReturn(aResponse().withBody(jsonFrom("openrtb2/trustx/test-trustx-bid-response.json"))));
+
+ // when
+ final Response response = responseFor("openrtb2/trustx/test-auction-trustx-request.json",
+ Endpoint.openrtb2_auction);
+
+ // then
+ assertJsonEquals("openrtb2/trustx/test-auction-trustx-response.json", response,
+ singletonList("trustx"));
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-request.json b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-request.json
index 9ee75999128..99e50554443 100644
--- a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-request.json
+++ b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-auction-resetdigital-request.json
@@ -9,8 +9,7 @@
},
"ext": {
"resetdigital": {
- "pubId": "lb.ads",
- "zoneId": "publisherTestID"
+ "placement_id": "publisherTestID"
}
}
}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-request.json
index 43d9d50841b..79219b2748f 100644
--- a/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-request.json
+++ b/src/test/resources/org/prebid/server/it/openrtb2/resetdigital/test-resetdigital-bid-request.json
@@ -8,11 +8,11 @@
"w": 300,
"h": 250
},
+ "tagid": "publisherTestID",
"ext": {
"tid": "${json-unit.any-string}",
"bidder": {
- "pubId": "lb.ads",
- "zoneId": "publisherTestID"
+ "placement_id": "publisherTestID"
}
}
}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-auction-trustx-request.json b/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-auction-trustx-request.json
new file mode 100644
index 00000000000..3a51299c968
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-auction-trustx-request.json
@@ -0,0 +1,35 @@
+{
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "data": {
+ "adserver": {
+ "adslot": "test_ad_slot"
+ }
+ },
+ "trustx": {
+ "uid": 1234
+ }
+ }
+ }
+ ],
+ "app": {
+ "bundle": "app-bundle"
+ },
+ "device": {
+ "ua": "ua",
+ "ip": "123.123.123.123"
+ },
+ "tmax": 5000,
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-auction-trustx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-auction-trustx-response.json
new file mode 100644
index 00000000000..c721e26850a
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-auction-trustx-response.json
@@ -0,0 +1,45 @@
+{
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "2315f4a3-f2c4-8cac-69de-985272e1d706",
+ "impid": "test-imp-id",
+ "price": 10,
+ "adm": "
",
+ "crid": "4235d4b1-e2a6-9bbb-a34c-766714c2d221",
+ "exp": 300,
+ "mtype": 1,
+ "ext": {
+ "prebid": {
+ "type": "banner",
+ "meta": {
+ "adaptercode": "trustx",
+ "networkName": "test-network"
+ }
+ },
+ "bidder" : {
+ "trustx" : {
+ "networkName" : "test-network"
+ }
+ },
+ "origbidcpm": 10
+ }
+ }
+ ],
+ "seat": "trustx",
+ "group": 0
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "trustx": "{{ trustedstack.response_time_ms }}"
+ },
+ "prebid": {
+ "auctiontimestamp": 0
+ },
+ "tmaxrequest": 5000
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-trustx-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-trustx-bid-request.json
new file mode 100644
index 00000000000..cae2a79df15
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-trustx-bid-request.json
@@ -0,0 +1,57 @@
+{
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "secure": 1,
+ "ext": {
+ "gpid": "test_ad_slot",
+ "data": {
+ "adserver": {
+ "adslot": "test_ad_slot"
+ }
+ },
+ "bidder": {
+ "uid": 1234
+ }
+ }
+ }
+ ],
+ "source": {
+ "tid": "${json-unit.any-string}"
+ },
+ "device": {
+ "ua": "ua",
+ "ip": "123.123.123.123"
+ },
+ "app": {
+ "bundle": "app-bundle"
+ },
+ "at": 1,
+ "tmax": "${json-unit.any-number}",
+ "cur": [
+ "USD"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "ext": {
+ "prebid": {
+ "channel": {
+ "name": "app"
+ },
+ "server": {
+ "externalurl": "http://localhost:8080",
+ "gvlid": 1,
+ "datacenter": "local",
+ "endpoint": "/openrtb2/auction"
+ }
+ }
+ }
+}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-trustx-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-trustx-bid-response.json
new file mode 100644
index 00000000000..db3f7d6a257
--- /dev/null
+++ b/src/test/resources/org/prebid/server/it/openrtb2/trustx/test-trustx-bid-response.json
@@ -0,0 +1,25 @@
+{
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "2315f4a3-f2c4-8cac-69de-985272e1d706",
+ "crid": "4235d4b1-e2a6-9bbb-a34c-766714c2d221",
+ "price": 10,
+ "impid": "test-imp-id",
+ "adm": "
",
+ "mtype": 1,
+ "ext": {
+ "bidder": {
+ "trustx": {
+ "networkName": "test-network"
+ }
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "seat": "trustx"
+}
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 ffa0e527254..5611899aa11 100644
--- a/src/test/resources/org/prebid/server/it/test-application.properties
+++ b/src/test/resources/org/prebid/server/it/test-application.properties
@@ -613,6 +613,8 @@ adapters.trafficgate.enabled=true
adapters.trafficgate.endpoint=http://localhost:8090/trafficgate-exchange
adapters.trustedstack.enabled=true
adapters.trustedstack.endpoint=http://localhost:8090/trustedstack-exchange
+adapters.trustx.enabled=true
+adapters.trustx.endpoint=http://localhost:8090/trustx-exchange
adapters.ucfunnel.enabled=true
adapters.ucfunnel.endpoint=http://localhost:8090/ucfunnel-exchange
adapters.undertone.enabled=true