From 5c2c4d836fdf48021b03ef2bffc3eebb99aea7ab Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:37:13 +0200 Subject: [PATCH] Bigoad: Add bidder (#3328) --- .../bidder/adelement/AdelementBidder.java | 2 +- .../server/bidder/bigoad/BigoadBidder.java | 150 +++++++++++ .../server/bidder/concert/ConcertBidder.java | 3 +- .../bidder/consumable/ConsumableBidder.java | 6 +- .../improvedigital/ImprovedigitalBidder.java | 2 +- .../org/prebid/server/bidder/ix/IxBidder.java | 6 +- .../server/bidder/mediago/MediaGoBidder.java | 8 +- .../bidder/orbidder/OrbidderBidder.java | 2 +- .../bidder/smrtconnect/SmrtconnectBidder.java | 2 +- .../ext/request/bigoad/ExtImpBigoad.java | 11 + .../config/bidder/BigoadConfiguration.java | 41 +++ src/main/resources/bidder-config/bigoad.yaml | 26 ++ .../static/bidder-params/bigoad.json | 15 ++ .../bidder/bigoad/BigoadBidderTest.java | 238 ++++++++++++++++++ .../java/org/prebid/server/it/BigoadTest.java | 32 +++ .../bigoad/test-auction-bigoad-request.json | 23 ++ .../bigoad/test-auction-bigoad-response.json | 39 +++ .../bigoad/test-bigoad-bid-request.json | 53 ++++ .../bigoad/test-bigoad-bid-response.json | 21 ++ .../server/it/test-application.properties | 2 + 20 files changed, 667 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/prebid/server/bidder/bigoad/BigoadBidder.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/bigoad/ExtImpBigoad.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/BigoadConfiguration.java create mode 100644 src/main/resources/bidder-config/bigoad.yaml create mode 100644 src/main/resources/static/bidder-params/bigoad.json create mode 100644 src/test/java/org/prebid/server/bidder/bigoad/BigoadBidderTest.java create mode 100644 src/test/java/org/prebid/server/it/BigoadTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-bigoad-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-bigoad-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/adelement/AdelementBidder.java b/src/main/java/org/prebid/server/bidder/adelement/AdelementBidder.java index c7e0443407b..c61cb88be40 100644 --- a/src/main/java/org/prebid/server/bidder/adelement/AdelementBidder.java +++ b/src/main/java/org/prebid/server/bidder/adelement/AdelementBidder.java @@ -103,7 +103,7 @@ private static BidType getBidType(Integer mType) { case 3 -> BidType.audio; case 4 -> BidType.xNative; - default -> throw new PreBidException("Unsupported mType " + mType); + case null, default -> throw new PreBidException("Unsupported mType " + mType); }; } } diff --git a/src/main/java/org/prebid/server/bidder/bigoad/BigoadBidder.java b/src/main/java/org/prebid/server/bidder/bigoad/BigoadBidder.java new file mode 100644 index 00000000000..7c24e7cf480 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/bigoad/BigoadBidder.java @@ -0,0 +1,150 @@ +package org.prebid.server.bidder.bigoad; + +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; +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.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.bigoad.ExtImpBigoad; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; +import org.prebid.server.util.HttpUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +public class BigoadBidder implements Bidder { + + private static final TypeReference> BIGOAD_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; + private static final String SSP_ID_MACRO = "{{SspId}}"; + private static final String OPEN_RTB_VERSION = "2.5"; + + private final String endpointUrl; + private final JacksonMapper mapper; + + public BigoadBidder(String endpointUrl, JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final List modifiedImps = new ArrayList<>(request.getImp()); + + final Imp firstImp = modifiedImps.getFirst(); + final ExtImpBigoad extImpBigoad; + try { + extImpBigoad = parseImpExt(firstImp); + } catch (PreBidException e) { + return Result.withError(BidderError.badInput(e.getMessage())); + } + + modifiedImps.set(0, modifyImp(firstImp, extImpBigoad)); + return Result.withValue(BidderUtil.defaultRequest( + updateBidRequest(request, modifiedImps), + headers(), + makeEndpointUrl(extImpBigoad), + mapper)); + } + + private ExtImpBigoad parseImpExt(Imp imp) throws PreBidException { + try { + return mapper.mapper().convertValue(imp.getExt(), BIGOAD_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException("imp %s: unable to unmarshal ext.bidder: %s" + .formatted(imp.getId(), e.getMessage())); + } + } + + private Imp modifyImp(Imp imp, ExtImpBigoad extImpBigoad) { + return imp.toBuilder().ext(mapper.mapper().valueToTree(extImpBigoad)).build(); + } + + private static BidRequest updateBidRequest(BidRequest bidRequest, List imps) { + return bidRequest.toBuilder().imp(imps).build(); + } + + private static MultiMap headers() { + return HttpUtil.headers().add(HttpUtil.X_OPENRTB_VERSION_HEADER, OPEN_RTB_VERSION); + } + + private String makeEndpointUrl(ExtImpBigoad extImpBigoadx) { + final String safeSspId = HttpUtil.encodeUrl(StringUtils.trimToEmpty(extImpBigoadx.getSspId())); + return endpointUrl.replace(SSP_ID_MACRO, safeSspId); + } + + @Override + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final List errors = new ArrayList<>(); + final BidResponse bidResponse = parseBidResponse(httpCall.getResponse()); + return Result.of(extractBids(bidResponse, errors), errors); + } catch (PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private BidResponse parseBidResponse(HttpResponse response) { + try { + return mapper.decodeValue(response.getBody(), BidResponse.class); + } catch (DecodeException e) { + throw new PreBidException("Bad server response: " + e.getMessage()); + } + } + + private static List extractBids(BidResponse bidResponse, List errors) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + throw new PreBidException("Empty SeatBid array"); + } + + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(Objects::nonNull) + .map(bid -> makeBid(bid, bidResponse.getCur(), errors)) + .filter(Objects::nonNull) + .toList(); + } + + private static BidderBid makeBid(Bid bid, String currency, List errors) { + final BidType bidType; + try { + bidType = resolveBidType(bid.getMtype(), bid.getImpid()); + } catch (PreBidException e) { + errors.add(BidderError.badServerResponse(e.getMessage())); + return null; + } + + return BidderBid.of(bid, bidType, currency); + } + + private static BidType resolveBidType(Integer mType, String impId) { + return switch (mType) { + case 1 -> BidType.banner; + case 2 -> BidType.video; + case 4 -> BidType.xNative; + case null, default -> throw new PreBidException("unrecognized bid type in response from bigoad " + impId); + }; + } +} diff --git a/src/main/java/org/prebid/server/bidder/concert/ConcertBidder.java b/src/main/java/org/prebid/server/bidder/concert/ConcertBidder.java index de9e3a53175..aaadcb08a89 100644 --- a/src/main/java/org/prebid/server/bidder/concert/ConcertBidder.java +++ b/src/main/java/org/prebid/server/bidder/concert/ConcertBidder.java @@ -148,7 +148,8 @@ private static BidType resolveBidType(Integer mType, String impId) { case 2 -> BidType.video; case 3 -> BidType.audio; case 4 -> throw new PreBidException("native media types are not yet supported"); - default -> throw new PreBidException("Failed to parse media type for bid: \"%s\"".formatted(impId)); + case null, default -> + throw new PreBidException("Failed to parse media type for bid: \"%s\"".formatted(impId)); }; } } diff --git a/src/main/java/org/prebid/server/bidder/consumable/ConsumableBidder.java b/src/main/java/org/prebid/server/bidder/consumable/ConsumableBidder.java index f636c3ff88c..344680b9aa4 100644 --- a/src/main/java/org/prebid/server/bidder/consumable/ConsumableBidder.java +++ b/src/main/java/org/prebid/server/bidder/consumable/ConsumableBidder.java @@ -171,13 +171,13 @@ private static BidType getBidType(Bid bid, List imps) { } private static Optional getBidTypeFromMtype(Integer mType) { - final BidType bidType = mType != null ? switch (mType) { + final BidType bidType = switch (mType) { case 1 -> BidType.banner; case 2 -> BidType.video; case 3 -> BidType.audio; case 4 -> BidType.xNative; - default -> null; - } : null; + case null, default -> null; + }; return Optional.ofNullable(bidType); } diff --git a/src/main/java/org/prebid/server/bidder/improvedigital/ImprovedigitalBidder.java b/src/main/java/org/prebid/server/bidder/improvedigital/ImprovedigitalBidder.java index 99b56d59f8a..a2cba1d3c36 100644 --- a/src/main/java/org/prebid/server/bidder/improvedigital/ImprovedigitalBidder.java +++ b/src/main/java/org/prebid/server/bidder/improvedigital/ImprovedigitalBidder.java @@ -250,7 +250,7 @@ private static BidType getBidType(Bid bid) { case 2 -> BidType.video; case 3 -> BidType.audio; case 4 -> BidType.xNative; - default -> throw new PreBidException( + case null, default -> throw new PreBidException( "Unsupported mtype %d for impression with ID: \"%s\"".formatted(bid.getMtype(), bid.getImpid())); }; } diff --git a/src/main/java/org/prebid/server/bidder/ix/IxBidder.java b/src/main/java/org/prebid/server/bidder/ix/IxBidder.java index 3dbf2deb920..5c7c468af8f 100644 --- a/src/main/java/org/prebid/server/bidder/ix/IxBidder.java +++ b/src/main/java/org/prebid/server/bidder/ix/IxBidder.java @@ -297,13 +297,13 @@ private static BidType getBidType(Bid bid, List imps) { } private static Optional getBidTypeFromMtype(Integer mType) { - final BidType bidType = mType != null ? switch (mType) { + final BidType bidType = switch (mType) { case 1 -> BidType.banner; case 2 -> BidType.video; case 3 -> BidType.audio; case 4 -> BidType.xNative; - default -> null; - } : null; + case null, default -> null; + }; return Optional.ofNullable(bidType); } diff --git a/src/main/java/org/prebid/server/bidder/mediago/MediaGoBidder.java b/src/main/java/org/prebid/server/bidder/mediago/MediaGoBidder.java index 389915b1816..69cc2cc52a5 100644 --- a/src/main/java/org/prebid/server/bidder/mediago/MediaGoBidder.java +++ b/src/main/java/org/prebid/server/bidder/mediago/MediaGoBidder.java @@ -70,7 +70,7 @@ public Result>> makeHttpRequests(BidRequest request } final List modifiedImps = new ArrayList<>(); - for (Imp imp: request.getImp()) { + for (Imp imp : request.getImp()) { final Imp modifiedImp = imp.toBuilder().banner(modifyBanner(imp.getBanner())).build(); modifiedImps.add(modifiedImp); } @@ -195,11 +195,11 @@ private static BidType getBidType(Bid bid, List imps) { } private static Optional getBidTypeFromMtype(Integer mType) { - final BidType bidType = mType != null ? switch (mType) { + final BidType bidType = switch (mType) { case 1 -> BidType.banner; case 4 -> BidType.xNative; - default -> null; - } : null; + case null, default -> null; + }; return Optional.ofNullable(bidType); } diff --git a/src/main/java/org/prebid/server/bidder/orbidder/OrbidderBidder.java b/src/main/java/org/prebid/server/bidder/orbidder/OrbidderBidder.java index f87b312d533..4565c91ee28 100644 --- a/src/main/java/org/prebid/server/bidder/orbidder/OrbidderBidder.java +++ b/src/main/java/org/prebid/server/bidder/orbidder/OrbidderBidder.java @@ -143,7 +143,7 @@ private static BidType getBidType(Integer mType) { case 3 -> BidType.audio; case 4 -> BidType.xNative; - default -> throw new PreBidException("Unsupported mType " + mType); + case null, default -> throw new PreBidException("Unsupported mType " + mType); }; } } diff --git a/src/main/java/org/prebid/server/bidder/smrtconnect/SmrtconnectBidder.java b/src/main/java/org/prebid/server/bidder/smrtconnect/SmrtconnectBidder.java index dd5d39b0346..1eb42956ec6 100644 --- a/src/main/java/org/prebid/server/bidder/smrtconnect/SmrtconnectBidder.java +++ b/src/main/java/org/prebid/server/bidder/smrtconnect/SmrtconnectBidder.java @@ -104,7 +104,7 @@ private static BidType getBidType(Integer mType) { case 3 -> BidType.audio; case 4 -> BidType.xNative; - default -> throw new PreBidException("Unsupported mType " + mType); + case null, default -> throw new PreBidException("Unsupported mType " + mType); }; } } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/bigoad/ExtImpBigoad.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/bigoad/ExtImpBigoad.java new file mode 100644 index 00000000000..c9377ca3bb2 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/bigoad/ExtImpBigoad.java @@ -0,0 +1,11 @@ +package org.prebid.server.proto.openrtb.ext.request.bigoad; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpBigoad { + + @JsonProperty("sspid") + String sspId; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/BigoadConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/BigoadConfiguration.java new file mode 100644 index 00000000000..bd3c932efc2 --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/BigoadConfiguration.java @@ -0,0 +1,41 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.bigoad.BigoadBidder; +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/bigoad.yaml", factory = YamlPropertySourceFactory.class) +public class BigoadConfiguration { + + private static final String BIDDER_NAME = "bigoad"; + + @Bean("bigoadConfigurationProperties") + @ConfigurationProperties("adapters.bigoad") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps bigoadBidderDeps(BidderConfigurationProperties bigoadConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(bigoadConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new BigoadBidder(config.getEndpoint(), mapper)) + .assemble(); + } +} diff --git a/src/main/resources/bidder-config/bigoad.yaml b/src/main/resources/bidder-config/bigoad.yaml new file mode 100644 index 00000000000..becccce1ff5 --- /dev/null +++ b/src/main/resources/bidder-config/bigoad.yaml @@ -0,0 +1,26 @@ +adapters: + bigoad: + endpoint: https://api.imotech.tech/Ad/GetAdOut?sspid={{SspId}} + geoscope: + - USA + - RUS + - JPN + - BRA + - KOR + - IDN + - TUR + - SAU + - MEX + endpoint-compression: gzip + meta-info: + maintainer-email: bigoads-prebid@bigo.sg + app-media-types: + - banner + - video + - native + site-media-types: + - banner + - video + - native + supported-vendors: + vendor-id: 0 diff --git a/src/main/resources/static/bidder-params/bigoad.json b/src/main/resources/static/bidder-params/bigoad.json new file mode 100644 index 00000000000..15d8c5981fa --- /dev/null +++ b/src/main/resources/static/bidder-params/bigoad.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "BigoAd Adapter Params", + "description": "A schema which validates params accepted by the BigoAd adapter", + "type": "object", + "properties": { + "sspid": { + "type": "string", + "description": "Special id provided by BigoAd" + } + }, + "required": [ + "sspid" + ] +} diff --git a/src/test/java/org/prebid/server/bidder/bigoad/BigoadBidderTest.java b/src/test/java/org/prebid/server/bidder/bigoad/BigoadBidderTest.java new file mode 100644 index 00000000000..7daca265211 --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/bigoad/BigoadBidderTest.java @@ -0,0 +1,238 @@ +package org.prebid.server.bidder.bigoad; + +import com.fasterxml.jackson.core.JsonProcessingException; +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.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.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.bigoad.ExtImpBigoad; +import org.prebid.server.util.HttpUtil; + +import java.util.List; +import java.util.Map; +import java.util.function.UnaryOperator; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.tuple; +import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; +import static org.prebid.server.proto.openrtb.ext.response.BidType.video; +import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; + +public class BigoadBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://domain.com/path?{{SspId}}"; + + private final BigoadBidder target = new BigoadBidder(ENDPOINT_URL, jacksonMapper); + + @Test + public void creationShouldFailOnInvalidEndPointUrl() { + // when and then + assertThatIllegalArgumentException().isThrownBy(() -> new BigoadBidder("invalid_url", jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldReturnErrorOnInvalidImpExt() { + // given + final BidRequest request = givenBidRequest(givenImp(imp -> imp.ext( + mapper.valueToTree(Map.of("bidder", emptyList()))))); + + // when + final Result>> result = target.makeHttpRequests(request); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(error.getMessage()) + .startsWith("imp null: unable to unmarshal ext.bidder: Cannot deserialize value of type"); + }); + } + + @Test + public void makeHttpRequestsShouldModifyFirstImp() { + // given + final BidRequest request = givenBidRequest( + givenImp(ExtImpBigoad.of("sspId0")), + givenImp(ExtImpBigoad.of("sspId1"))); + + // when + final Result>> result = target.makeHttpRequests(request); + + // then + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .containsExactly( + givenImp(imp -> imp.ext(mapper.valueToTree(ExtImpBigoad.of("sspId0")))), + givenImp(ExtImpBigoad.of("sspId1"))); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnOpenRTBHeader() { + // given + final BidRequest request = givenBidRequest(givenImp(ExtImpBigoad.of("sspId"))); + + // when + final Result>> result = target.makeHttpRequests(request); + + // then + assertThat(result.getValue()) + .extracting(HttpRequest::getHeaders) + .flatExtracting(MultiMap::entries) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .contains(tuple(HttpUtil.X_OPENRTB_VERSION_HEADER.toString(), "2.5")); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnExpectedUri() { + // given + final BidRequest request = givenBidRequest( + givenImp(ExtImpBigoad.of("sspId0")), + givenImp(ExtImpBigoad.of("sspId1"))); + + // when + final Result>> result = target.makeHttpRequests(request); + + // then + assertThat(result.getValue()) + .extracting(HttpRequest::getUri) + .containsExactly("https://domain.com/path?sspId0"); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { + // given + final BidderCall httpCall = givenHttpCall("invalid"); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(error.getMessage()).startsWith("Bad server response: Failed to decode: Unrecognized token"); + }); + } + + @Test + public void makeBidsShouldReturnErrorIfBidResponseIsNull() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(null)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).containsExactly(BidderError.badServerResponse("Empty SeatBid array")); + } + + @Test + public void makeBidsShouldReturnErrorIfBidResponseSeatBidIsNull() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + mapper.writeValueAsString(BidResponse.builder().build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()) + .containsExactly(BidderError.badServerResponse("Empty SeatBid array")); + } + + @Test + public void makeBidsShouldReturnErrorOnAudio() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(givenBidResponse(givenBid(bid -> bid.mtype(3)))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()) + .containsExactly(BidderError.badServerResponse("unrecognized bid type in response from bigoad null")); + } + + @Test + public void makeBidsShouldReturnErrorOnInvalidMtype() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(givenBidResponse(givenBid(bid -> bid.mtype(0)))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()) + .containsExactly(BidderError.badServerResponse("unrecognized bid type in response from bigoad null")); + } + + @Test + public void makeBidsShouldReturnExpectedResult() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(givenBidResponse( + givenBid(bid -> bid.mtype(1)), + givenBid(bid -> bid.mtype(2)), + givenBid(bid -> bid.mtype(4)))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(banner, video, xNative); + assertThat(result.getErrors()).isEmpty(); + } + + private static BidRequest givenBidRequest(Imp... imps) { + return BidRequest.builder().imp(asList(imps)).build(); + } + + private static Imp givenImp(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder()).build(); + } + + private static Imp givenImp(ExtImpBigoad extImpBigoad) { + return givenImp(imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, extImpBigoad)))); + } + + private static String givenBidResponse(Bid... bids) throws JsonProcessingException { + return mapper.writeValueAsString(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(asList(bids)).build())) + .build()); + } + + private static Bid givenBid(UnaryOperator bidCustomizer) { + return bidCustomizer.apply(Bid.builder()).build(); + } + + private static BidderCall givenHttpCall(String body) { + return BidderCall.succeededHttp( + HttpRequest.builder().payload(null).build(), + HttpResponse.of(200, null, body), null); + } +} diff --git a/src/test/java/org/prebid/server/it/BigoadTest.java b/src/test/java/org/prebid/server/it/BigoadTest.java new file mode 100644 index 00000000000..f9b6851681e --- /dev/null +++ b/src/test/java/org/prebid/server/it/BigoadTest.java @@ -0,0 +1,32 @@ +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 BigoadTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromBigoad() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/bigoad-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/bigoad/test-bigoad-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/bigoad/test-bigoad-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/bigoad/test-auction-bigoad-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/bigoad/test-auction-bigoad-response.json", response, singletonList("bigoad")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-request.json b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-request.json new file mode 100644 index 00000000000..f6ad11e0aa8 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-request.json @@ -0,0 +1,23 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bigoad": { + "sspid": "sspId" + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-response.json new file mode 100644 index 00000000000..287c05c61aa --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-auction-bigoad-response.json @@ -0,0 +1,39 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "mtype": 1, + "price": 3.33, + "adm": "adm001", + "adid": "adid001", + "cid": "cid001", + "crid": "crid001", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + }, + "origbidcpm": 3.33 + } + } + ], + "seat": "bigoad", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "bigoad": "{{ bigoad.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-bigoad-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-bigoad-bid-request.json new file mode 100644 index 00000000000..e66913a60fb --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-bigoad-bid-request.json @@ -0,0 +1,53 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "sspid": "sspId" + } + } + ], + "source": { + "tid": "${json-unit.any-string}" + }, + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": "${json-unit.any-number}", + "cur": [ + "USD" + ], + "regs": { + "ext": { + "gdpr": 0 + } + }, + "ext": { + "prebid": { + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-bigoad-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-bigoad-bid-response.json new file mode 100644 index 00000000000..24dd4214ec6 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/bigoad/test-bigoad-bid-response.json @@ -0,0 +1,21 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "mtype": 1, + "price": 3.33, + "adid": "adid001", + "crid": "crid001", + "cid": "cid001", + "adm": "adm001", + "h": 250, + "w": 300 + } + ] + } + ] +} 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 dd629c5f15a..2a06064defa 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -127,6 +127,8 @@ adapters.bidscube.enabled=true adapters.bidscube.endpoint=http://localhost:8090/bidscube-exchange adapters.bidstack.enabled=true adapters.bidstack.endpoint=http://localhost:8090/bidstack-exchange +adapters.bigoad.enabled=true +adapters.bigoad.endpoint=http://localhost:8090/bigoad-exchange adapters.bizzclick.enabled=true adapters.bizzclick.endpoint=http://localhost:8090/bizzclick-exchange?host={{Host}}&source={{SourceId}}&account={{AccountID}} adapters.bliink.enabled=true