From 8d3377d8e95f458c8792f05ff16139d5f69000dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Wed, 15 May 2024 14:22:25 +0200 Subject: [PATCH 01/18] ReadPeak : New adapter (#3142) --- .../bidder/readpeak/ReadPeakBidder.java | 130 ++++++++++++++++++ .../ext/request/readpeak/ExtImpReadPeak.java | 22 +++ .../config/bidder/ReadPeakConfiguration.java | 41 ++++++ .../resources/bidder-config/readpeak.yaml | 15 ++ .../static/bidder-params/readpeak.json | 25 ++++ .../org/prebid/server/it/ReadPeakTest.java | 35 +++++ .../test-auction-readpeak-request.json | 23 ++++ .../test-auction-readpeak-response.json | 34 +++++ .../readpeak/test-readpeak-bid-request.json | 56 ++++++++ .../readpeak/test-readpeak-bid-response.json | 16 +++ .../server/it/test-application.properties | 2 + 11 files changed, 399 insertions(+) create mode 100644 src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/ReadPeakConfiguration.java create mode 100644 src/main/resources/bidder-config/readpeak.yaml create mode 100644 src/main/resources/static/bidder-params/readpeak.json create mode 100644 src/test/java/org/prebid/server/it/ReadPeakTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java new file mode 100644 index 00000000000..6a8c29dc4bd --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -0,0 +1,130 @@ +package org.prebid.server.bidder.readpeak; + +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.http.HttpMethod; +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.readpeak.ExtImpReadPeak; +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.Collections; +import java.util.List; +import java.util.Objects; + +public class ReadPeakBidder implements Bidder { + + private static final TypeReference> READPEAK_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; + + private static final String PUBLISHER_ID_MACRO = "{{PublisherId}}"; + + private final String endpointUrl; + private final JacksonMapper mapper; + + public ReadPeakBidder(String endpointUrl, JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final List> httpRequests = new ArrayList<>(); + final List errors = new ArrayList<>(); + + for (Imp imp : request.getImp()) { + try { + final ExtImpReadPeak extImp = parseImpExt(imp); + httpRequests.add(makeHttpRequest(request, extImp)); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + + return Result.of(httpRequests, errors); + } + + private ExtImpReadPeak parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), READPEAK_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException("Failed to deserialize Bematterfull extension: " + e.getMessage()); + } + } + + private HttpRequest makeHttpRequest(BidRequest bidRequest, ExtImpReadPeak extImp) { + return HttpRequest.builder() + .method(HttpMethod.POST) + .uri(makeUrl(extImp)) + .headers(HttpUtil.headers()) + .body(mapper.encodeToBytes(bidRequest)) + .impIds(BidderUtil.impIds(bidRequest)) + .payload(bidRequest) + .build(); + } + + private String makeUrl(ExtImpReadPeak extImp) { + return endpointUrl + .replace(PUBLISHER_ID_MACRO, StringUtils.defaultString(extImp.getPublisherId())); + } + + @Override + public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + return Result.withValues(extractBids(bidResponse)); + } catch (DecodeException | PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private static List extractBids(BidResponse bidResponse) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + return Collections.emptyList(); + } + return bidsFromResponse(bidResponse); + } + + private static List bidsFromResponse(BidResponse bidResponse) { + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .map(bid -> BidderBid.of(bid, getBidMediaType(bid), bidResponse.getCur())) + .toList(); + } + + private static BidType getBidMediaType(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.xNative; + default -> throw new PreBidException( + "Unable to resolve mediaType " + bid.getMtype() + " for bid: " + bid.getId()); + }; + } +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java new file mode 100644 index 00000000000..1fb4d6e3d94 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java @@ -0,0 +1,22 @@ +package org.prebid.server.proto.openrtb.ext.request.readpeak; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +import java.math.BigDecimal; + +@Value(staticConstructor = "of") +public class ExtImpReadPeak { + + @JsonProperty("publisherId") + String publisherId; + + @JsonProperty("siteId") + BigDecimal siteId; + + @JsonProperty("bidfloor") + BigDecimal bidFloor; + + @JsonProperty("tagId") + BigDecimal tagId; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ReadPeakConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ReadPeakConfiguration.java new file mode 100644 index 00000000000..0859c097400 --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/ReadPeakConfiguration.java @@ -0,0 +1,41 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.bematterfull.BematterfullBidder; +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/readpeak.yaml", factory = YamlPropertySourceFactory.class) +public class ReadPeakConfiguration { + + private static final String BIDDER_NAME = "readpeak"; + + @Bean("readpeakConfigurationProperties") + @ConfigurationProperties("adapters.readpeak") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps readpeakBidderDeps(BidderConfigurationProperties readpeakConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(readpeakConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new BematterfullBidder(config.getEndpoint(), mapper)) + .assemble(); + } +} diff --git a/src/main/resources/bidder-config/readpeak.yaml b/src/main/resources/bidder-config/readpeak.yaml new file mode 100644 index 00000000000..0982f1f9d17 --- /dev/null +++ b/src/main/resources/bidder-config/readpeak.yaml @@ -0,0 +1,15 @@ +adapters: + readpeak: + endpoint: https://dsp.readpeak.com/header/prebid + geoscope: + - EEA + meta-info: + maintainer-email: devteam@readpeak.com + app-media-types: + - banner + - native + site-media-types: + - banner + - native + supported-vendors: + vendor-id: 290 diff --git a/src/main/resources/static/bidder-params/readpeak.json b/src/main/resources/static/bidder-params/readpeak.json new file mode 100644 index 00000000000..274aadb92e0 --- /dev/null +++ b/src/main/resources/static/bidder-params/readpeak.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Readpeak Adapter Params", + "description": "A schema which validates params accepted by the Readpeak adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "Publisher ID provided by Readpeak" + }, + "siteId": { + "type": "string", + "description": "Site/Media ID provided by Readpeak" + }, + "bidfloor": { + "type": "number", + "description": "CPM Bid Floor" + }, + "tagId": { + "type": "string", + "description": "Ad placement identifier" + } + }, + "required": ["publisherId"] +} diff --git a/src/test/java/org/prebid/server/it/ReadPeakTest.java b/src/test/java/org/prebid/server/it/ReadPeakTest.java new file mode 100644 index 00000000000..5f1aa65337d --- /dev/null +++ b/src/test/java/org/prebid/server/it/ReadPeakTest.java @@ -0,0 +1,35 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.prebid.server.model.Endpoint; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.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; + +@RunWith(SpringRunner.class) +public class ReadPeakTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromLoyal() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/readpeak-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/readpeak/test-readpeak-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/loyal/test-readpeak-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/readpeak/test-auction-readpeak-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/readpeak/test-auction-readpeak-response.json", response, singletonList("readpeak")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-request.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-request.json new file mode 100644 index 00000000000..811388f5487 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-request.json @@ -0,0 +1,23 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "readpeak": { + "publisherId": "testPublisherId" + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json new file mode 100644 index 00000000000..25774577057 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json @@ -0,0 +1,34 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 3.33, + "crid": "creativeId", + "mtype": 1, + "ext": { + "origbidcpm": 3.33, + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "readpeak", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "readpeak": "{{ readpeak.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json new file mode 100644 index 00000000000..7e8d0f14464 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json @@ -0,0 +1,56 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "publisherId": "testPublisherId" + } + } + } + ], + "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/readpeak/test-readpeak-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-response.json new file mode 100644 index 00000000000..6922c116b46 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-response.json @@ -0,0 +1,16 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 3.33, + "crid": "creativeId", + "mtype": 1 + } + ] + } + ] +} 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 e4f8a793e55..b8311511208 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -308,6 +308,8 @@ adapters.pulsepoint.enabled=true adapters.pulsepoint.endpoint=http://localhost:8090/pulsepoint-exchange adapters.pwbid.enabled=true adapters.pwbid.endpoint=http://localhost:8090/pwbid-exchange +adapters.readpeak.enabled=true +adapters.readpeak.endpoint=http://localhost:8090/readpeak-exchange?pubId={{PublisherId}} adapters.relevantdigital.enabled=true adapters.relevantdigital.endpoint=http://localhost:8090/relevantdigital-exchange?pbsHost={{Host}} adapters.resetdigital.enabled=true From 2fa23433e95255669c17951835388bb66801b04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Thu, 16 May 2024 11:27:49 +0200 Subject: [PATCH 02/18] ReadPeak : new Adapter --- src/main/resources/bidder-config/readpeak.yaml | 2 +- src/test/java/org/prebid/server/it/ReadPeakTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/bidder-config/readpeak.yaml b/src/main/resources/bidder-config/readpeak.yaml index 0982f1f9d17..3ba30730d7d 100644 --- a/src/main/resources/bidder-config/readpeak.yaml +++ b/src/main/resources/bidder-config/readpeak.yaml @@ -1,6 +1,6 @@ adapters: readpeak: - endpoint: https://dsp.readpeak.com/header/prebid + endpoint: https://dsp.readpeak.com/header/prebid?p={{PublisherId}} geoscope: - EEA meta-info: diff --git a/src/test/java/org/prebid/server/it/ReadPeakTest.java b/src/test/java/org/prebid/server/it/ReadPeakTest.java index 5f1aa65337d..961d0adad0b 100644 --- a/src/test/java/org/prebid/server/it/ReadPeakTest.java +++ b/src/test/java/org/prebid/server/it/ReadPeakTest.java @@ -23,7 +23,7 @@ public void openrtb2AuctionShouldRespondWithBidsFromLoyal() throws IOException, // given WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/readpeak-exchange")) .withRequestBody(equalToJson(jsonFrom("openrtb2/readpeak/test-readpeak-bid-request.json"))) - .willReturn(aResponse().withBody(jsonFrom("openrtb2/loyal/test-readpeak-bid-response.json")))); + .willReturn(aResponse().withBody(jsonFrom("openrtb2/readpeak/test-readpeak-bid-response.json")))); // when final Response response = responseFor("openrtb2/readpeak/test-auction-readpeak-request.json", From f151fbd48635c70f84f77c57f8a18fc9a80abacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Thu, 16 May 2024 14:22:30 +0200 Subject: [PATCH 03/18] ReadPeak : new Adapter --- .../bidder/readpeak/ReadPeakBidder.java | 7 +- .../ext/request/readpeak/ExtImpReadPeak.java | 8 +- .../config/bidder/ReadPeakConfiguration.java | 4 +- .../bidder/readpeak/ReadPeakBidderTest.java | 290 ++++++++++++++++++ .../org/prebid/server/it/ReadPeakTest.java | 2 +- 5 files changed, 299 insertions(+), 12 deletions(-) create mode 100644 src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index 6a8c29dc4bd..2189a541b72 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -26,7 +26,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Objects; @@ -67,7 +66,7 @@ private ExtImpReadPeak parseImpExt(Imp imp) { try { return mapper.mapper().convertValue(imp.getExt(), READPEAK_EXT_TYPE_REFERENCE).getBidder(); } catch (IllegalArgumentException e) { - throw new PreBidException("Failed to deserialize Bematterfull extension: " + e.getMessage()); + throw new PreBidException("Failed to deserialize ReadPeak extension: " + e.getMessage()); } } @@ -99,7 +98,7 @@ public final Result> makeBids(BidderCall httpCall, B private static List extractBids(BidResponse bidResponse) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { - return Collections.emptyList(); + throw new PreBidException("Empty SeatBid array"); } return bidsFromResponse(bidResponse); } @@ -124,7 +123,7 @@ private static BidType getBidMediaType(Bid bid) { case 1 -> BidType.banner; case 2 -> BidType.xNative; default -> throw new PreBidException( - "Unable to resolve mediaType " + bid.getMtype() + " for bid: " + bid.getId()); + "Unable to fetch mediaType " + bid.getMtype() + " in multi-format: " + bid.getImpid()); }; } } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java index 1fb4d6e3d94..5ae980c81ed 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java @@ -3,8 +3,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Value; -import java.math.BigDecimal; - @Value(staticConstructor = "of") public class ExtImpReadPeak { @@ -12,11 +10,11 @@ public class ExtImpReadPeak { String publisherId; @JsonProperty("siteId") - BigDecimal siteId; + String siteId; @JsonProperty("bidfloor") - BigDecimal bidFloor; + String bidFloor; @JsonProperty("tagId") - BigDecimal tagId; + String tagId; } diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ReadPeakConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ReadPeakConfiguration.java index 0859c097400..351d6b121ac 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/ReadPeakConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/ReadPeakConfiguration.java @@ -1,7 +1,7 @@ package org.prebid.server.spring.config.bidder; import org.prebid.server.bidder.BidderDeps; -import org.prebid.server.bidder.bematterfull.BematterfullBidder; +import org.prebid.server.bidder.readpeak.ReadPeakBidder; import org.prebid.server.json.JacksonMapper; import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties; import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; @@ -35,7 +35,7 @@ BidderDeps readpeakBidderDeps(BidderConfigurationProperties readpeakConfiguratio return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(readpeakConfigurationProperties) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new BematterfullBidder(config.getEndpoint(), mapper)) + .bidderCreator(config -> new ReadPeakBidder(config.getEndpoint(), mapper)) .assemble(); } } diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java new file mode 100644 index 00000000000..336f64ef529 --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -0,0 +1,290 @@ +package org.prebid.server.bidder.readpeak; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import io.vertx.core.http.HttpMethod; +import org.junit.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.readpeak.ExtImpReadPeak; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.prebid.server.bidder.model.BidderError.badServerResponse; +import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; +import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; +import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER; +import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE; +import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER; +import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; + +public class ReadPeakBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://test.com/test?param={{PublisherId}}"; + + private final ReadPeakBidder target = new ReadPeakBidder(ENDPOINT_URL, jacksonMapper); + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException().isThrownBy(() -> new ReadPeakBidder("invalid_url", jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldReturnExpectedBody() { + // given + final BidRequest bidRequest = givenBidRequest(); + + // when + final Result>> results = target.makeHttpRequests(bidRequest); + + // then + assertThat(results.getValue()) + .hasSize(1) + .first().satisfies(request -> assertThat(request.getBody()) + .isEqualTo(jacksonMapper.encodeToBytes(bidRequest))) + .satisfies(request -> assertThat(request.getPayload()).isEqualTo(bidRequest)); + assertThat(results.getErrors()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnExpectedImpIds() { + // given + final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + + // when + final Result>> results = target.makeHttpRequests(bidRequest); + + // then + assertThat(results.getValue()).hasSize(1).first() + .satisfies(request -> assertThat(request.getImpIds()).isEqualTo(Set.of("123"))); + assertThat(results.getErrors()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnExpectedHeaders() { + // given + final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()) + .extracting(HttpRequest::getHeaders) + .allSatisfy(headers -> { + assertThat(headers.get(CONTENT_TYPE_HEADER)).isEqualTo(APPLICATION_JSON_CONTENT_TYPE); + assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE); + }); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnErrorWhenRequestHasOnlyInvalidImpression() { + // given + final ObjectNode invalidExt = mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())); + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(invalidExt)); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> { + assertThat(error.getMessage()).startsWith("Failed to deserialize ReadPeak extension"); + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); + } + ); + } + + @Test + public void shouldReplacePlacementIdMacro() { + // given + final BidRequest bidRequest = + givenBidRequest(impBuilder -> impBuilder + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpReadPeak + .of("placement123", null, null, null))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue()).extracting(HttpRequest::getUri) + .containsExactly("https://test.com/test?param=placement123"); + } + + @Test + public void makeBidsShouldReturnErrorWhenResponseCanNotBeParsed() { + // given + final BidderCall httpCall = givenHttpCall(null, "invalid"); + + // when + final Result> actual = target.makeBids(httpCall, null); + + // then + assertThat(actual.getValue()).isEmpty(); + assertThat(actual.getErrors()).allSatisfy(error -> { + assertThat(error.getMessage()).startsWith("Failed to decode"); + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + }); + } + + @Test + public void makeBidsShouldReturnErrorWhenResponseDoesNotHaveSeatBid() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, givenBidResponse()); + + // when + final Result> actual = target.makeBids(httpCall, null); + + // then + assertThat(actual.getValue()).isEmpty(); + assertThat(actual.getErrors()).containsExactly(badServerResponse("Empty SeatBid array")); + } + + @Test + public void makeBidsShouldHandleEmptySeatbid() { + // given + final HttpRequest httpRequest = HttpRequest.builder().method(HttpMethod.POST).uri("https://test.com").body(new byte[0]).build(); + final HttpResponse httpResponse = HttpResponse.of(200, null, "{\"seatbid\": []}"); + final BidderCall httpCall = BidderCall.storedHttp(httpRequest, httpResponse); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isNotEmpty(); + assertThat(result.getErrors().get(0).getMessage()).isEqualTo("Empty SeatBid array"); + } + + @Test + public void makeBidsShouldReturnAllFourBidTypesSuccessfully() throws JsonProcessingException { + // given + final Bid bannerBid = Bid.builder().impid("1").mtype(1).build(); + final Bid nativeBid = Bid.builder().impid("2").mtype(2).build(); + + final BidderCall httpCall = givenHttpCall(givenBidRequest(), + givenBidResponse(bannerBid, nativeBid)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(bannerBid, banner, "USD"), BidderBid.of(nativeBid, xNative, "USD")); + } + + @Test + public void makeBidsShouldReturnBannerBidSuccessfully() throws JsonProcessingException { + // given + final Bid bannerBid = Bid.builder().impid("1").mtype(1).build(); + + final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bannerBid)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(bannerBid, banner, "USD")); + + } + + @Test + public void makeBidsShouldReturnNativeBidSuccessfully() throws JsonProcessingException { + // given + final Bid nativeBid = Bid.builder().impid("2").mtype(2).build(); + + final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(nativeBid)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly(BidderBid.of(nativeBid, xNative, "USD")); + } + + @Test + public void makeBidsShouldReturnErrorWhenImpTypeIsNotSupported() throws JsonProcessingException { + // given + final Bid nativeBid = Bid.builder().impid("id").mtype(3).build(); + final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(nativeBid)); + + // when + final Result> result = target.makeBids(httpCall, null); + // then + assertThat(result.getErrors()) + .containsExactly(BidderError.badServerResponse("Unable to fetch mediaType 3 in multi-format: id")); + } + + @Test + public void makeBidsShouldReturnErrorWhenImpTypeIsNull() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("id").mtype(null).build(); + final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bid)); + + // when + final Result> result = target.makeBids(httpCall, null); + // then + assertThat(result.getErrors()) + .containsExactly(BidderError.badServerResponse("Missing MType for bid: " + bid.getId())); + } + + private static BidRequest givenBidRequest() { + final Imp imp = Imp.builder().id("imp_id") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpReadPeak.of("{{PublisherId}}", "{{SiteId}}", "{{Bidfloor}}", "{{TagId}}")))).build(); + return BidRequest.builder().imp(List.of(imp)).build(); + } + + private static BidRequest givenBidRequest(UnaryOperator... impCustomizers) { + return BidRequest.builder() + .device(Device.builder().ua("ua").ip("ip").ipv6("ipv6").build()) + .imp(Arrays.stream(impCustomizers).map(ReadPeakBidderTest::givenImp).toList()) + .build(); + } + + private static Imp givenImp(Function impCustomizer) { + return impCustomizer.apply(Imp.builder().id("123").banner(Banner.builder().w(23).h(25).build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpReadPeak + .of("publisherId", null, null, null))))).build(); + } + + private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { + return BidderCall.succeededHttp(HttpRequest.builder() + .payload(bidRequest).build(), HttpResponse.of(200, null, body), null); + } + + private static String givenBidResponse(Bid... bids) throws JsonProcessingException { + return mapper.writeValueAsString( + BidResponse.builder() + .cur("USD") + .seatbid(bids.length == 0 + ? Collections.emptyList() + : List.of(SeatBid.builder().bid(List.of(bids)).build())) + .build()); + } +} diff --git a/src/test/java/org/prebid/server/it/ReadPeakTest.java b/src/test/java/org/prebid/server/it/ReadPeakTest.java index 961d0adad0b..2bf13a7c43b 100644 --- a/src/test/java/org/prebid/server/it/ReadPeakTest.java +++ b/src/test/java/org/prebid/server/it/ReadPeakTest.java @@ -19,7 +19,7 @@ public class ReadPeakTest extends IntegrationTest { @Test - public void openrtb2AuctionShouldRespondWithBidsFromLoyal() throws IOException, JSONException { + public void openrtb2AuctionShouldRespondWithBidsFromReadPeak() throws IOException, JSONException { // given WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/readpeak-exchange")) .withRequestBody(equalToJson(jsonFrom("openrtb2/readpeak/test-readpeak-bid-request.json"))) From 1e7ec8b05677b49c9bf4b5590f8e746c0eb9a1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Mon, 20 May 2024 14:15:04 +0200 Subject: [PATCH 04/18] resolve comments --- .../bidder/readpeak/ReadPeakBidder.java | 89 ++++++++++++++----- .../ext/request/readpeak/ExtImpReadPeak.java | 8 +- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index 2189a541b72..8c83617f8cd 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -1,6 +1,7 @@ package org.prebid.server.bidder.readpeak; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; import com.iab.openrtb.response.Bid; @@ -10,20 +11,20 @@ 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.bidder.adtarget.proto.AdtargetImpExt; +import org.prebid.server.bidder.model.*; 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.adtarget.ExtImpAdtarget; +import org.prebid.server.proto.openrtb.ext.request.alkimi.ExtImpAlkimi; import org.prebid.server.proto.openrtb.ext.request.readpeak.ExtImpReadPeak; 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.List; @@ -45,20 +46,64 @@ public ReadPeakBidder(String endpointUrl, JacksonMapper mapper) { this.mapper = Objects.requireNonNull(mapper); } +// @Override +// public Result>> makeHttpRequests(BidRequest request) { +// final List> httpRequests = new ArrayList<>(); +// final List errors = new ArrayList<>(); +// +// for (Imp imp : request.getImp()) { +// try { +// final ExtImpReadPeak extImp = parseImpExt(imp); +// httpRequests.add(makeHttpRequest(request, extImp)); +// } catch (PreBidException e) { +// errors.add(BidderError.badInput(e.getMessage())); +// } +// } +// +// return Result.of(httpRequests, errors); +// } + @Override public Result>> makeHttpRequests(BidRequest request) { final List> httpRequests = new ArrayList<>(); final List errors = new ArrayList<>(); + // Create a deep copy of the incoming request + BidRequest requestCopy; + try { + requestCopy = mapper.decodeValue(mapper.encodeToBytes(request), BidRequest.class); + } catch (Exception e) { + errors.add(BidderError.badInput(e.getMessage())); + return Result.withErrors(errors); + } + + final List imps = new ArrayList<>(); for (Imp imp : request.getImp()) { + final ExtImpReadPeak extImpReadPeak; try { - final ExtImpReadPeak extImp = parseImpExt(imp); - httpRequests.add(makeHttpRequest(request, extImp)); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); + extImpReadPeak = parseImpExt(imp); + final Imp updatedImp = updateImp(imp, extImpReadPeak); + imps.add(updatedImp); + } catch (IllegalArgumentException e) { + errors.add(BidderError.badInput("Invalid Imp ext: " + e.getMessage())); } } + if (imps.isEmpty()) { + errors.add(BidderError.badInput("No valid impressions found")); + return Result.withErrors(errors); + } + + requestCopy = requestCopy.toBuilder().imp(imps).build(); + + final String url = makeUrl(imps.get(0).getExt().get("bidder").get("publisherId").asText()); + httpRequests.add(HttpRequest.builder() + .method(HttpMethod.POST) + .uri(url) + .headers(BidderUtil.headers()) + .payload(requestCopy) + .build()); + return Result.of(httpRequests, errors); } @@ -70,22 +115,26 @@ private ExtImpReadPeak parseImpExt(Imp imp) { } } - private HttpRequest makeHttpRequest(BidRequest bidRequest, ExtImpReadPeak extImp) { - return HttpRequest.builder() - .method(HttpMethod.POST) - .uri(makeUrl(extImp)) - .headers(HttpUtil.headers()) - .body(mapper.encodeToBytes(bidRequest)) - .impIds(BidderUtil.impIds(bidRequest)) - .payload(bidRequest) + private Imp updateImp(Imp imp, ExtImpReadPeak extImpReadPeak) { + final Price bidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); + + return imp.toBuilder() + .bidfloor(BidderUtil.isValidPrice(bidFloorPrice) + ? bidFloorPrice.getValue() + : extImpReadPeak.getBidFloor()) + .tagid(extImpReadPeak.getTagId()) .build(); } - private String makeUrl(ExtImpReadPeak extImp) { - return endpointUrl - .replace(PUBLISHER_ID_MACRO, StringUtils.defaultString(extImp.getPublisherId())); + private String makeUrl(String publisherId) { + return endpointUrl.replace(PUBLISHER_ID_MACRO, StringUtils.defaultString(publisherId)); } +// private String makeUrl(ExtImpReadPeak extImp) { +// return endpointUrl +// .replace(PUBLISHER_ID_MACRO, StringUtils.defaultString(extImp.getPublisherId())); +// } + @Override public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java index 5ae980c81ed..2d9732a7eb4 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java @@ -1,9 +1,13 @@ package org.prebid.server.proto.openrtb.ext.request.readpeak; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; import lombok.Value; -@Value(staticConstructor = "of") +import java.math.BigDecimal; + +@Value +@Builder(toBuilder = true) public class ExtImpReadPeak { @JsonProperty("publisherId") @@ -13,7 +17,7 @@ public class ExtImpReadPeak { String siteId; @JsonProperty("bidfloor") - String bidFloor; + BigDecimal bidFloor; @JsonProperty("tagId") String tagId; From 68a1434b61904406b7e2e25de2023f81d1630450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Mon, 20 May 2024 18:37:01 +0200 Subject: [PATCH 05/18] resolve comments --- .../bidder/readpeak/ReadPeakBidder.java | 92 ++++++++++--------- .../ext/request/readpeak/ExtImpReadPeak.java | 4 +- .../bidder/readpeak/ReadPeakBidderTest.java | 40 ++++++-- 3 files changed, 80 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index 8c83617f8cd..cb069b3d944 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -7,26 +7,26 @@ import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; -import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Bidder; -import org.prebid.server.bidder.adtarget.proto.AdtargetImpExt; -import org.prebid.server.bidder.model.*; +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.bidder.model.Price; 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.adtarget.ExtImpAdtarget; -import org.prebid.server.proto.openrtb.ext.request.alkimi.ExtImpAlkimi; import org.prebid.server.proto.openrtb.ext.request.readpeak.ExtImpReadPeak; import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta; 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.List; import java.util.Objects; @@ -46,23 +46,6 @@ public ReadPeakBidder(String endpointUrl, JacksonMapper mapper) { this.mapper = Objects.requireNonNull(mapper); } -// @Override -// public Result>> makeHttpRequests(BidRequest request) { -// final List> httpRequests = new ArrayList<>(); -// final List errors = new ArrayList<>(); -// -// for (Imp imp : request.getImp()) { -// try { -// final ExtImpReadPeak extImp = parseImpExt(imp); -// httpRequests.add(makeHttpRequest(request, extImp)); -// } catch (PreBidException e) { -// errors.add(BidderError.badInput(e.getMessage())); -// } -// } -// -// return Result.of(httpRequests, errors); -// } - @Override public Result>> makeHttpRequests(BidRequest request) { final List> httpRequests = new ArrayList<>(); @@ -97,13 +80,7 @@ public Result>> makeHttpRequests(BidRequest request requestCopy = requestCopy.toBuilder().imp(imps).build(); final String url = makeUrl(imps.get(0).getExt().get("bidder").get("publisherId").asText()); - httpRequests.add(HttpRequest.builder() - .method(HttpMethod.POST) - .uri(url) - .headers(BidderUtil.headers()) - .payload(requestCopy) - .build()); - + httpRequests.add(BidderUtil.defaultRequest(requestCopy, url, mapper)); return Result.of(httpRequests, errors); } @@ -130,38 +107,59 @@ private String makeUrl(String publisherId) { return endpointUrl.replace(PUBLISHER_ID_MACRO, StringUtils.defaultString(publisherId)); } -// private String makeUrl(ExtImpReadPeak extImp) { -// return endpointUrl -// .replace(PUBLISHER_ID_MACRO, StringUtils.defaultString(extImp.getPublisherId())); -// } - @Override public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return Result.withValues(extractBids(bidResponse)); + return Result.withValues(extractBids(bidResponse, mapper)); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private static List extractBids(BidResponse bidResponse) { + private static List extractBids(BidResponse bidResponse, JacksonMapper mapper) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { throw new PreBidException("Empty SeatBid array"); } - return bidsFromResponse(bidResponse); + return bidsFromResponse(bidResponse, mapper); } - private static List bidsFromResponse(BidResponse bidResponse) { + private static List bidsFromResponse(BidResponse bidResponse, JacksonMapper mapper) { return bidResponse.getSeatbid().stream() .filter(Objects::nonNull) .map(SeatBid::getBid) .filter(Objects::nonNull) - .flatMap(Collection::stream) - .map(bid -> BidderBid.of(bid, getBidMediaType(bid), bidResponse.getCur())) + .flatMap(List::stream) + .map(bid -> { + resolveMacros(bid); + // Dodanie metadanych do obiektu Bid + final ObjectNode ext = bid.getExt() != null ? bid.getExt().deepCopy() + : mapper.mapper().createObjectNode(); + ext.set("prebid", mapper.mapper().convertValue(getBidMeta(bid), ObjectNode.class)); + bid = bid.toBuilder().ext(ext).build(); + return BidderBid.of(bid, getBidMediaType(bid), bidResponse.getCur()); + }) .toList(); } + private static void resolveMacros(Bid bid) { + if (bid != null && bid.getPrice() != null) { + final String price = bid.getPrice().toPlainString(); + bid = bid.toBuilder() + .nurl(replaceMacro(bid.getNurl(), price)) + .adm(replaceMacro(bid.getAdm(), price)) + .burl(replaceMacro(bid.getBurl(), price)) + .build(); + } + } + + private static String replaceMacro(String url, String price) { + if (url != null) { + return url.replace("${AUCTION_PRICE}", price); + } + return url; + } + private static BidType getBidMediaType(Bid bid) { final Integer markupType = bid.getMtype(); if (markupType == null) { @@ -171,8 +169,14 @@ private static BidType getBidMediaType(Bid bid) { return switch (markupType) { case 1 -> BidType.banner; case 2 -> BidType.xNative; - default -> throw new PreBidException( - "Unable to fetch mediaType " + bid.getMtype() + " in multi-format: " + bid.getImpid()); + default -> throw new PreBidException("Unable to fetch mediaType " + markupType + + " in multi-format: " + bid.getImpid()); }; } + + private static ExtBidPrebidMeta getBidMeta(Bid bid) { + return ExtBidPrebidMeta.builder() + .advertiserDomains(bid.getAdomain()) + .build(); + } } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java index 2d9732a7eb4..5ed1e14d64c 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/readpeak/ExtImpReadPeak.java @@ -1,13 +1,11 @@ package org.prebid.server.proto.openrtb.ext.request.readpeak; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Builder; import lombok.Value; import java.math.BigDecimal; -@Value -@Builder(toBuilder = true) +@Value(staticConstructor = "of") public class ExtImpReadPeak { @JsonProperty("publisherId") diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index 336f64ef529..ad36af6dea3 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -21,6 +21,7 @@ import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.readpeak.ExtImpReadPeak; +import java.math.BigDecimal; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -110,7 +111,7 @@ public void makeHttpRequestsShouldReturnErrorWhenRequestHasOnlyInvalidImpression // then assertThat(result.getValue()).isEmpty(); assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> { - assertThat(error.getMessage()).startsWith("Failed to deserialize ReadPeak extension"); + assertThat(error.getMessage()).startsWith("Invalid Imp ext: "); assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); } ); @@ -181,8 +182,8 @@ public void makeBidsShouldHandleEmptySeatbid() { @Test public void makeBidsShouldReturnAllFourBidTypesSuccessfully() throws JsonProcessingException { // given - final Bid bannerBid = Bid.builder().impid("1").mtype(1).build(); - final Bid nativeBid = Bid.builder().impid("2").mtype(2).build(); + final Bid bannerBid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN).build(); + final Bid nativeBid = Bid.builder().impid("2").mtype(2).price(BigDecimal.TEN).build(); final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bannerBid, nativeBid)); @@ -199,7 +200,7 @@ public void makeBidsShouldReturnAllFourBidTypesSuccessfully() throws JsonProcess @Test public void makeBidsShouldReturnBannerBidSuccessfully() throws JsonProcessingException { // given - final Bid bannerBid = Bid.builder().impid("1").mtype(1).build(); + final Bid bannerBid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN).build(); final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bannerBid)); @@ -209,13 +210,12 @@ public void makeBidsShouldReturnBannerBidSuccessfully() throws JsonProcessingExc // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).containsOnly(BidderBid.of(bannerBid, banner, "USD")); - } @Test public void makeBidsShouldReturnNativeBidSuccessfully() throws JsonProcessingException { // given - final Bid nativeBid = Bid.builder().impid("2").mtype(2).build(); + final Bid nativeBid = Bid.builder().impid("2").mtype(2).price(BigDecimal.TEN).build(); final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(nativeBid)); @@ -230,7 +230,7 @@ public void makeBidsShouldReturnNativeBidSuccessfully() throws JsonProcessingExc @Test public void makeBidsShouldReturnErrorWhenImpTypeIsNotSupported() throws JsonProcessingException { // given - final Bid nativeBid = Bid.builder().impid("id").mtype(3).build(); + final Bid nativeBid = Bid.builder().impid("id").mtype(3).price(BigDecimal.TEN).build(); final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(nativeBid)); // when @@ -243,7 +243,7 @@ public void makeBidsShouldReturnErrorWhenImpTypeIsNotSupported() throws JsonProc @Test public void makeBidsShouldReturnErrorWhenImpTypeIsNull() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("id").mtype(null).build(); + final Bid bid = Bid.builder().impid("id").mtype(null).price(BigDecimal.TEN).build(); final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bid)); // when @@ -253,10 +253,32 @@ public void makeBidsShouldReturnErrorWhenImpTypeIsNull() throws JsonProcessingEx .containsExactly(BidderError.badServerResponse("Missing MType for bid: " + bid.getId())); } + @Test + public void makeBidsShouldResolveMacrosInBids() throws JsonProcessingException { + // given + final Bid bid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN) + .nurl("http://example.com?price=${AUCTION_PRICE}") + .adm("
${AUCTION_PRICE}
") + .burl("http://example.com?price=${AUCTION_PRICE}") + .build(); + + final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bid)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + final Bid resolvedBid = result.getValue().get(0).getBid(); + assertThat(resolvedBid.getNurl()).isEqualTo("http://example.com?price=10"); + assertThat(resolvedBid.getAdm()).isEqualTo("
10
"); + assertThat(resolvedBid.getBurl()).isEqualTo("http://example.com?price=10"); + } + private static BidRequest givenBidRequest() { final Imp imp = Imp.builder().id("imp_id") .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpReadPeak.of("{{PublisherId}}", "{{SiteId}}", "{{Bidfloor}}", "{{TagId}}")))).build(); + ExtImpReadPeak.of("{{PublisherId}}", "{{SiteId}}", BigDecimal.valueOf(0.5), "{{TagId}}")))).build(); return BidRequest.builder().imp(List.of(imp)).build(); } From 49a0d30401cab89091b7b509b9e07f9003224652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Tue, 21 May 2024 16:44:48 +0200 Subject: [PATCH 06/18] resolve comments --- .../bidder/readpeak/ReadPeakBidder.java | 10 +- .../bidder/readpeak/ReadPeakBidderTest.java | 131 +++++++++++++++++- 2 files changed, 136 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index cb069b3d944..e81109b72af 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -65,8 +65,10 @@ public Result>> makeHttpRequests(BidRequest request final ExtImpReadPeak extImpReadPeak; try { extImpReadPeak = parseImpExt(imp); - final Imp updatedImp = updateImp(imp, extImpReadPeak); - imps.add(updatedImp); + if (extImpReadPeak != null) { + final Imp updatedImp = updateImp(imp, extImpReadPeak); + imps.add(updatedImp); + } } catch (IllegalArgumentException e) { errors.add(BidderError.badInput("Invalid Imp ext: " + e.getMessage())); } @@ -109,6 +111,10 @@ private String makeUrl(String publisherId) { @Override public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + if (httpCall.getResponse() == null || httpCall.getResponse().getBody() == null) { + return Result.withError(BidderError.badServerResponse("Empty response body")); + } + try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); return Result.withValues(extractBids(bidResponse, mapper)); diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index ad36af6dea3..9a0a5bb317d 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -46,7 +46,7 @@ public class ReadPeakBidderTest extends VertxTest { private final ReadPeakBidder target = new ReadPeakBidder(ENDPOINT_URL, jacksonMapper); @Test - public void creationShouldFailOnInvalidEndpointUrl() { + public void creationShouldFailOnInvalidEndpointUrl2() { assertThatIllegalArgumentException().isThrownBy(() -> new ReadPeakBidder("invalid_url", jacksonMapper)); } @@ -180,7 +180,7 @@ public void makeBidsShouldHandleEmptySeatbid() { } @Test - public void makeBidsShouldReturnAllFourBidTypesSuccessfully() throws JsonProcessingException { + public void makeBidsShouldReturnAllTwoBidTypesSuccessfully() throws JsonProcessingException { // given final Bid bannerBid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN).build(); final Bid nativeBid = Bid.builder().impid("2").mtype(2).price(BigDecimal.TEN).build(); @@ -275,10 +275,135 @@ public void makeBidsShouldResolveMacrosInBids() throws JsonProcessingException { assertThat(resolvedBid.getBurl()).isEqualTo("http://example.com?price=10"); } +// @Test +// public void creationShouldFailOnInvalidEndpointUrl() { +// assertThatIllegalArgumentException().isThrownBy(() -> new ReadPeakBidder("invalid_url", jacksonMapper)); +// } +// +// @Test +// public void makeHttpRequestsShouldReturnErrorIfImpExtCannotBeParsed() { +// // given +// final BidRequest bidRequest = BidRequest.builder() +// .imp(Collections.singletonList(Imp.builder() +// .ext(mapper.createObjectNode()) +// .build())) +// .build(); +// +// // when +// final Result>> result = target.makeHttpRequests(bidRequest); +// +// // then +// assertThat(result.getErrors()).hasSize(1) +// .element(0) +// .extracting(BidderError::getType) +// .isEqualTo(BidderError.Type.bad_input); +// } + +// @Test +// public void makeHttpRequestsShouldReturnErrorIfExtImpReadPeakCannotBeParsed() { +// // given +// final ObjectNode ext = mapper.createObjectNode(); +// ext.put("bidder", "invalid"); +// +// final BidRequest bidRequest = BidRequest.builder() +// .imp(Collections.singletonList(Imp.builder() +// .ext(ext) +// .build())) +// .build(); +// +// // when +// final Result>> result = target.makeHttpRequests(bidRequest); +// +// // then +// assertThat(result.getErrors()).hasSize(1) +// .element(0) +// .extracting(BidderError::getType) +// .isEqualTo(BidderError.Type.bad_input); +// } +// +// @Test +// public void makeHttpRequestsShouldReturnHttpRequest() { +// // given +// final ExtImpReadPeak extImpReadPeak = ExtImpReadPeak.of("testPublisherId", "testSiteId", BigDecimal.valueOf(1.5), "testTagId"); +// final ObjectNode ext = mapper.createObjectNode(); +// ext.set("bidder", mapper.valueToTree(extImpReadPeak)); +// +// final BidRequest bidRequest = BidRequest.builder() +// .imp(Collections.singletonList(Imp.builder() +// .id("123") +// .ext(ext) +// .build())) +// .build(); +// +// // when +// final Result>> result = target.makeHttpRequests(bidRequest); +// +// // then +// assertThat(result.getErrors()).isEmpty(); +// assertThat(result.getValue()).hasSize(1) +// .element(0) +// .extracting(HttpRequest::getBody) +// .isNotNull(); +// } +// +// @Test +// public void makeBidsShouldReturnEmptyResultIfResponseBodyIsEmpty() { +// // given +// final BidderCall bidderCall = BidderCall.succeededHttp(null, HttpResponse.of(200, null, ""), null); +// +// // when +// final Result> result = target.makeBids(bidderCall, null); +// +// // then +// assertThat(result.getErrors()).isEmpty(); +// assertThat(result.getValue()).isEmpty(); +// } +// +// @Test +// public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { +// // given +// final HttpResponse httpResponse = HttpResponse.of(200, null, "invalid"); +// final BidderCall bidderCall = BidderCall.succeededHttp(null, httpResponse, null); +// +// // when +// final Result> result = target.makeBids(bidderCall, null); +// +// // then +// assertThat(result.getErrors()).hasSize(1) +// .element(0) +// .extracting(BidderError::getType) +// .isEqualTo(BidderError.Type.bad_server_response); +// } +// +// @Test +// public void makeBidsShouldReturnBids() throws Exception { +// // given +// final BidRequest bidRequest = BidRequest.builder().build(); +// final BidResponse bidResponse = BidResponse.builder() +// .seatbid(Collections.singletonList(SeatBid.builder() +// .bid(Collections.singletonList(com.iab.openrtb.response.Bid.builder().mtype(1).build())) +// .build())) +// .build(); +// +// final String bidResponseBody = mapper.writeValueAsString(bidResponse); +// final HttpResponse httpResponse = HttpResponse.of(200, null, bidResponseBody); +// final BidderCall bidderCall = BidderCall.succeededHttp(null, httpResponse, null); +// +// // when +// final Result> result = target.makeBids(bidderCall, bidRequest); +// +// // then +// assertThat(result.getErrors()).isEmpty(); +// assertThat(result.getValue()).hasSize(1); +// } + private static BidRequest givenBidRequest() { final Imp imp = Imp.builder().id("imp_id") + .tagid("TagId") // Dodaj tagid + .bidfloor(BigDecimal.valueOf(0.5)) // Dodaj bidfloor .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpReadPeak.of("{{PublisherId}}", "{{SiteId}}", BigDecimal.valueOf(0.5), "{{TagId}}")))).build(); + ExtImpReadPeak + .of("PublisherId", "SiteId", BigDecimal.valueOf(0.5), "TagId")))).build(); return BidRequest.builder().imp(List.of(imp)).build(); } From 6e608983d5da67cedafbc6af7ea731e4b771ee74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Wed, 22 May 2024 11:53:51 +0200 Subject: [PATCH 07/18] resolve comments --- .../bidder/readpeak/ReadPeakBidder.java | 76 +++++++++---------- .../bidder/readpeak/ReadPeakBidderTest.java | 45 +++++++---- 2 files changed, 67 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index e81109b72af..00bead40d61 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -26,6 +26,7 @@ import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -37,6 +38,7 @@ public class ReadPeakBidder implements Bidder { }; private static final String PUBLISHER_ID_MACRO = "{{PublisherId}}"; + private static final String PRICE_MACRO = "${AUCTION_PRICE}"; private final String endpointUrl; private final JacksonMapper mapper; @@ -117,67 +119,63 @@ public final Result> makeBids(BidderCall httpCall, B try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return Result.withValues(extractBids(bidResponse, mapper)); + return Result.withValues(extractBids(bidResponse, mapper, bidRequest)); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private static List extractBids(BidResponse bidResponse, JacksonMapper mapper) { + private static List extractBids(BidResponse bidResponse, JacksonMapper mapper, BidRequest bidRequest) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { throw new PreBidException("Empty SeatBid array"); } - return bidsFromResponse(bidResponse, mapper); + return bidsFromResponse(bidResponse, mapper, bidRequest); } - private static List bidsFromResponse(BidResponse bidResponse, JacksonMapper mapper) { + private static List bidsFromResponse(BidResponse bidResponse, JacksonMapper mapper, BidRequest bidRequest) { return bidResponse.getSeatbid().stream() .filter(Objects::nonNull) .map(SeatBid::getBid) .filter(Objects::nonNull) .flatMap(List::stream) - .map(bid -> { - resolveMacros(bid); - // Dodanie metadanych do obiektu Bid - final ObjectNode ext = bid.getExt() != null ? bid.getExt().deepCopy() - : mapper.mapper().createObjectNode(); - ext.set("prebid", mapper.mapper().convertValue(getBidMeta(bid), ObjectNode.class)); - bid = bid.toBuilder().ext(ext).build(); - return BidderBid.of(bid, getBidMediaType(bid), bidResponse.getCur()); - }) +// .map(bid -> { +// resolveMacros(bid); +// +// .map(bid -> BidderBid.of(resolveMacros(bid), getMediaType(bid.getImpid(), bidRequest.getImp()), +// bidResponse.getCur())); +// final ObjectNode ext = bid.getExt() != null ? bid.getExt().deepCopy() +// : mapper.mapper().createObjectNode(); +// ext.set("prebid", mapper.mapper().convertValue(getBidMeta(bid), ObjectNode.class)); +// bid = bid.toBuilder().ext(ext).build(); +// return BidderBid.of(bid, getBidMediaType(bid), bidResponse.getCur()); +// }) + .map(bid -> BidderBid.of(resolveMacros(bid), resolveBidType(bid.getImpid(), bidRequest.getImp()), + bidResponse.getCur())) + .filter(Objects::nonNull) .toList(); } - private static void resolveMacros(Bid bid) { - if (bid != null && bid.getPrice() != null) { - final String price = bid.getPrice().toPlainString(); - bid = bid.toBuilder() - .nurl(replaceMacro(bid.getNurl(), price)) - .adm(replaceMacro(bid.getAdm(), price)) - .burl(replaceMacro(bid.getBurl(), price)) - .build(); - } - } + private static Bid resolveMacros(Bid bid) { + final BigDecimal price = bid.getPrice(); + final String priceAsString = price != null ? price.toPlainString() : "0"; - private static String replaceMacro(String url, String price) { - if (url != null) { - return url.replace("${AUCTION_PRICE}", price); - } - return url; + return bid.toBuilder() + .nurl(StringUtils.replace(bid.getNurl(), PRICE_MACRO, priceAsString)) + .adm(StringUtils.replace(bid.getAdm(), PRICE_MACRO, priceAsString)) + .build(); } - private static BidType getBidMediaType(Bid bid) { - final Integer markupType = bid.getMtype(); - if (markupType == null) { - throw new PreBidException("Missing MType for bid: " + bid.getId()); + private static BidType resolveBidType(String impId, List imps) { + for (Imp imp : imps) { + if (imp.getId().equals(impId)) { + if (imp.getBanner() != null) { + return BidType.banner; + } else if (imp.getXNative() != null) { + return BidType.xNative; + } + } } - - return switch (markupType) { - case 1 -> BidType.banner; - case 2 -> BidType.xNative; - default -> throw new PreBidException("Unable to fetch mediaType " + markupType - + " in multi-format: " + bid.getImpid()); - }; + throw new PreBidException("Failed to find banner/native impression \"%s\"".formatted(impId)); } private static ExtBidPrebidMeta getBidMeta(Bid bid) { diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index 9a0a5bb317d..29c50d19c45 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -29,8 +29,10 @@ import java.util.function.Function; import java.util.function.UnaryOperator; +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.bidder.model.BidderError.badServerResponse; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; @@ -254,25 +256,28 @@ public void makeBidsShouldReturnErrorWhenImpTypeIsNull() throws JsonProcessingEx } @Test - public void makeBidsShouldResolveMacrosInBids() throws JsonProcessingException { + public void makeBidsShouldReturnBidWithResolvedMacros() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN) - .nurl("http://example.com?price=${AUCTION_PRICE}") - .adm("
${AUCTION_PRICE}
") - .burl("http://example.com?price=${AUCTION_PRICE}") - .build(); + final BidRequest bidRequest = givenBidRequest(); - final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bid)); + final BidderCall httpCall = givenHttpCall(bidRequest, mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder + .impid("1") + .nurl("https://mynurl.example.com/win/232332?price=${AUCTION_PRICE}") + .adm("

") + .price(BigDecimal.valueOf(13.45))))); // when - final Result> result = target.makeBids(httpCall, null); + final Result> result = target.makeBids(httpCall, bidRequest); // then assertThat(result.getErrors()).isEmpty(); - final Bid resolvedBid = result.getValue().get(0).getBid(); - assertThat(resolvedBid.getNurl()).isEqualTo("http://example.com?price=10"); - assertThat(resolvedBid.getAdm()).isEqualTo("
10
"); - assertThat(resolvedBid.getBurl()).isEqualTo("http://example.com?price=10"); + assertThat(result.getValue()) + .extracting(BidderBid::getBid) + .extracting(Bid::getNurl, Bid::getAdm) + .containsExactly(tuple("https://mynurl.example.com/win/232332?price=13.45", + "

")); } // @Test @@ -398,7 +403,8 @@ public void makeBidsShouldResolveMacrosInBids() throws JsonProcessingException { // } private static BidRequest givenBidRequest() { - final Imp imp = Imp.builder().id("imp_id") + final Imp imp = Imp.builder() + .id("imp_id") .tagid("TagId") // Dodaj tagid .bidfloor(BigDecimal.valueOf(0.5)) // Dodaj bidfloor .ext(mapper.valueToTree(ExtPrebid.of(null, @@ -421,8 +427,17 @@ private static Imp givenImp(Function impCustomiz } private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { - return BidderCall.succeededHttp(HttpRequest.builder() - .payload(bidRequest).build(), HttpResponse.of(200, null, body), null); + return BidderCall.succeededHttp( + HttpRequest.builder().payload(bidRequest).build(), + HttpResponse.of(200, null, body), + null); + } + + private static BidResponse givenBidResponse(Function bidCustomizer) { + return BidResponse.builder() + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bidCustomizer.apply(Bid.builder()).build())) + .build())) + .build(); } private static String givenBidResponse(Bid... bids) throws JsonProcessingException { From 209780ff7cdc90427f04150e43f8ffbabf7f6172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Wed, 22 May 2024 15:18:18 +0200 Subject: [PATCH 08/18] New adapter: ReadPeak --- .../bidder/readpeak/ReadPeakBidder.java | 121 ++++++------- .../bidder/readpeak/ReadPeakBidderTest.java | 171 ++---------------- .../readpeak/test-readpeak-bid-request.json | 2 +- 3 files changed, 76 insertions(+), 218 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index 00bead40d61..e3b366b3a9a 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -1,6 +1,7 @@ package org.prebid.server.bidder.readpeak; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; @@ -22,7 +23,6 @@ import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.readpeak.ExtImpReadPeak; import org.prebid.server.proto.openrtb.ext.response.BidType; -import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta; import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; public class ReadPeakBidder implements Bidder { @@ -37,7 +38,6 @@ public class ReadPeakBidder implements Bidder { new TypeReference<>() { }; - private static final String PUBLISHER_ID_MACRO = "{{PublisherId}}"; private static final String PRICE_MACRO = "${AUCTION_PRICE}"; private final String endpointUrl; @@ -53,38 +53,21 @@ public Result>> makeHttpRequests(BidRequest request final List> httpRequests = new ArrayList<>(); final List errors = new ArrayList<>(); - // Create a deep copy of the incoming request - BidRequest requestCopy; - try { - requestCopy = mapper.decodeValue(mapper.encodeToBytes(request), BidRequest.class); - } catch (Exception e) { - errors.add(BidderError.badInput(e.getMessage())); - return Result.withErrors(errors); - } - - final List imps = new ArrayList<>(); for (Imp imp : request.getImp()) { final ExtImpReadPeak extImpReadPeak; try { extImpReadPeak = parseImpExt(imp); - if (extImpReadPeak != null) { - final Imp updatedImp = updateImp(imp, extImpReadPeak); - imps.add(updatedImp); - } - } catch (IllegalArgumentException e) { - errors.add(BidderError.badInput("Invalid Imp ext: " + e.getMessage())); + final Imp modifiedImp = modifyImp(imp, extImpReadPeak); + httpRequests.add(makeHttpRequest(request, modifiedImp)); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); } } - if (imps.isEmpty()) { - errors.add(BidderError.badInput("No valid impressions found")); - return Result.withErrors(errors); + if (httpRequests.isEmpty()) { + return Result.withError(BidderError.badInput("found no valid impressions")); } - requestCopy = requestCopy.toBuilder().imp(imps).build(); - - final String url = makeUrl(imps.get(0).getExt().get("bidder").get("publisherId").asText()); - httpRequests.add(BidderUtil.defaultRequest(requestCopy, url, mapper)); return Result.of(httpRequests, errors); } @@ -96,7 +79,7 @@ private ExtImpReadPeak parseImpExt(Imp imp) { } } - private Imp updateImp(Imp imp, ExtImpReadPeak extImpReadPeak) { + private Imp modifyImp(Imp imp, ExtImpReadPeak extImpReadPeak) { final Price bidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); return imp.toBuilder() @@ -107,50 +90,42 @@ private Imp updateImp(Imp imp, ExtImpReadPeak extImpReadPeak) { .build(); } - private String makeUrl(String publisherId) { - return endpointUrl.replace(PUBLISHER_ID_MACRO, StringUtils.defaultString(publisherId)); + private HttpRequest makeHttpRequest(BidRequest request, Imp imp) { + final BidRequest outgoingRequest = request.toBuilder().imp(List.of(imp)).build(); + + return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); } @Override - public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { - if (httpCall.getResponse() == null || httpCall.getResponse().getBody() == null) { - return Result.withError(BidderError.badServerResponse("Empty response body")); - } - + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return Result.withValues(extractBids(bidResponse, mapper, bidRequest)); + final List bids = extractBids(bidResponse); + return Result.withValues(bids); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private static List extractBids(BidResponse bidResponse, JacksonMapper mapper, BidRequest bidRequest) { + private List extractBids(BidResponse bidResponse) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { throw new PreBidException("Empty SeatBid array"); } - return bidsFromResponse(bidResponse, mapper, bidRequest); + return bidsFromResponse(bidResponse); } - private static List bidsFromResponse(BidResponse bidResponse, JacksonMapper mapper, BidRequest bidRequest) { + private List bidsFromResponse(BidResponse bidResponse) { return bidResponse.getSeatbid().stream() .filter(Objects::nonNull) .map(SeatBid::getBid) .filter(Objects::nonNull) .flatMap(List::stream) -// .map(bid -> { -// resolveMacros(bid); -// -// .map(bid -> BidderBid.of(resolveMacros(bid), getMediaType(bid.getImpid(), bidRequest.getImp()), -// bidResponse.getCur())); -// final ObjectNode ext = bid.getExt() != null ? bid.getExt().deepCopy() -// : mapper.mapper().createObjectNode(); -// ext.set("prebid", mapper.mapper().convertValue(getBidMeta(bid), ObjectNode.class)); -// bid = bid.toBuilder().ext(ext).build(); -// return BidderBid.of(bid, getBidMediaType(bid), bidResponse.getCur()); -// }) - .map(bid -> BidderBid.of(resolveMacros(bid), resolveBidType(bid.getImpid(), bidRequest.getImp()), - bidResponse.getCur())) + .map(bid -> { + final Bid resolvedBid = resolveMacros(bid); + final BidType bidType = getBidType(bid); + final Bid updatedBid = addBidMeta(resolvedBid); + return BidderBid.of(updatedBid, bidType, bidResponse.getCur()); + }) .filter(Objects::nonNull) .toList(); } @@ -165,22 +140,42 @@ private static Bid resolveMacros(Bid bid) { .build(); } - private static BidType resolveBidType(String impId, List imps) { - for (Imp imp : imps) { - if (imp.getId().equals(impId)) { - if (imp.getBanner() != null) { - return BidType.banner; - } else if (imp.getXNative() != null) { - return BidType.xNative; - } - } + private BidType getBidType(Bid bid) { + final JsonNode typeNode = Optional.ofNullable(bid.getExt()) + .map(extNode -> extNode.get("prebid")) + .map(extPrebidNode -> extPrebidNode.get("type")) + .orElse(null); + + final BidType bidType; + try { + bidType = mapper.mapper().convertValue(typeNode, BidType.class); + } catch (IllegalArgumentException e) { + throw new PreBidException("Failed to parse bid.ext.prebid.type for bid.id: '%s'" + .formatted(bid.getId())); + } + + if (bidType == null) { + throw new PreBidException("bid.ext.prebid.type is not present for bid.id: '%s'" + .formatted(bid.getId())); } - throw new PreBidException("Failed to find banner/native impression \"%s\"".formatted(impId)); + + return switch (bidType) { + case banner, xNative -> bidType; + default -> throw new PreBidException("Unsupported BidType: " + + bidType.getName() + " for bid.id: '" + bid.getId() + "'"); + }; } - private static ExtBidPrebidMeta getBidMeta(Bid bid) { - return ExtBidPrebidMeta.builder() - .advertiserDomains(bid.getAdomain()) + private Bid addBidMeta(Bid bid) { + final ObjectNode bidExt = bid.getExt() != null + ? bid.getExt().deepCopy() + : mapper.mapper().createObjectNode(); + + final ObjectNode prebidNode = bidExt.putObject("prebid"); + final ObjectNode metaNode = prebidNode.putObject("meta"); + metaNode.set("advertiserDomains", mapper.mapper().valueToTree(bid.getAdomain())); + return bid.toBuilder() + .ext(bidExt) .build(); } } diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index 29c50d19c45..856fa220626 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -29,10 +29,8 @@ import java.util.function.Function; import java.util.function.UnaryOperator; -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.bidder.model.BidderError.badServerResponse; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; @@ -48,7 +46,7 @@ public class ReadPeakBidderTest extends VertxTest { private final ReadPeakBidder target = new ReadPeakBidder(ENDPOINT_URL, jacksonMapper); @Test - public void creationShouldFailOnInvalidEndpointUrl2() { + public void creationShouldFailOnInvalidEndpointUrl() { assertThatIllegalArgumentException().isThrownBy(() -> new ReadPeakBidder("invalid_url", jacksonMapper)); } @@ -182,7 +180,7 @@ public void makeBidsShouldHandleEmptySeatbid() { } @Test - public void makeBidsShouldReturnAllTwoBidTypesSuccessfully() throws JsonProcessingException { + public void makeBidsShouldReturnAllFourBidTypesSuccessfully() throws JsonProcessingException { // given final Bid bannerBid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN).build(); final Bid nativeBid = Bid.builder().impid("2").mtype(2).price(BigDecimal.TEN).build(); @@ -256,155 +254,29 @@ public void makeBidsShouldReturnErrorWhenImpTypeIsNull() throws JsonProcessingEx } @Test - public void makeBidsShouldReturnBidWithResolvedMacros() throws JsonProcessingException { + public void makeBidsShouldResolveMacrosInBids() throws JsonProcessingException { // given - final BidRequest bidRequest = givenBidRequest(); + final Bid bid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN) + .nurl("http://example.com?price=${AUCTION_PRICE}") + .adm("
${AUCTION_PRICE}
") + .burl("http://example.com?price=${AUCTION_PRICE}") + .build(); - final BidderCall httpCall = givenHttpCall(bidRequest, mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder - .impid("1") - .nurl("https://mynurl.example.com/win/232332?price=${AUCTION_PRICE}") - .adm("

") - .price(BigDecimal.valueOf(13.45))))); + final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bid)); // when - final Result> result = target.makeBids(httpCall, bidRequest); + final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(BidderBid::getBid) - .extracting(Bid::getNurl, Bid::getAdm) - .containsExactly(tuple("https://mynurl.example.com/win/232332?price=13.45", - "

")); + final Bid resolvedBid = result.getValue().get(0).getBid(); + assertThat(resolvedBid.getNurl()).isEqualTo("http://example.com?price=10"); + assertThat(resolvedBid.getAdm()).isEqualTo("
10
"); + assertThat(resolvedBid.getBurl()).isEqualTo("http://example.com?price=10"); } -// @Test -// public void creationShouldFailOnInvalidEndpointUrl() { -// assertThatIllegalArgumentException().isThrownBy(() -> new ReadPeakBidder("invalid_url", jacksonMapper)); -// } -// -// @Test -// public void makeHttpRequestsShouldReturnErrorIfImpExtCannotBeParsed() { -// // given -// final BidRequest bidRequest = BidRequest.builder() -// .imp(Collections.singletonList(Imp.builder() -// .ext(mapper.createObjectNode()) -// .build())) -// .build(); -// -// // when -// final Result>> result = target.makeHttpRequests(bidRequest); -// -// // then -// assertThat(result.getErrors()).hasSize(1) -// .element(0) -// .extracting(BidderError::getType) -// .isEqualTo(BidderError.Type.bad_input); -// } - -// @Test -// public void makeHttpRequestsShouldReturnErrorIfExtImpReadPeakCannotBeParsed() { -// // given -// final ObjectNode ext = mapper.createObjectNode(); -// ext.put("bidder", "invalid"); -// -// final BidRequest bidRequest = BidRequest.builder() -// .imp(Collections.singletonList(Imp.builder() -// .ext(ext) -// .build())) -// .build(); -// -// // when -// final Result>> result = target.makeHttpRequests(bidRequest); -// -// // then -// assertThat(result.getErrors()).hasSize(1) -// .element(0) -// .extracting(BidderError::getType) -// .isEqualTo(BidderError.Type.bad_input); -// } -// -// @Test -// public void makeHttpRequestsShouldReturnHttpRequest() { -// // given -// final ExtImpReadPeak extImpReadPeak = ExtImpReadPeak.of("testPublisherId", "testSiteId", BigDecimal.valueOf(1.5), "testTagId"); -// final ObjectNode ext = mapper.createObjectNode(); -// ext.set("bidder", mapper.valueToTree(extImpReadPeak)); -// -// final BidRequest bidRequest = BidRequest.builder() -// .imp(Collections.singletonList(Imp.builder() -// .id("123") -// .ext(ext) -// .build())) -// .build(); -// -// // when -// final Result>> result = target.makeHttpRequests(bidRequest); -// -// // then -// assertThat(result.getErrors()).isEmpty(); -// assertThat(result.getValue()).hasSize(1) -// .element(0) -// .extracting(HttpRequest::getBody) -// .isNotNull(); -// } -// -// @Test -// public void makeBidsShouldReturnEmptyResultIfResponseBodyIsEmpty() { -// // given -// final BidderCall bidderCall = BidderCall.succeededHttp(null, HttpResponse.of(200, null, ""), null); -// -// // when -// final Result> result = target.makeBids(bidderCall, null); -// -// // then -// assertThat(result.getErrors()).isEmpty(); -// assertThat(result.getValue()).isEmpty(); -// } -// -// @Test -// public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { -// // given -// final HttpResponse httpResponse = HttpResponse.of(200, null, "invalid"); -// final BidderCall bidderCall = BidderCall.succeededHttp(null, httpResponse, null); -// -// // when -// final Result> result = target.makeBids(bidderCall, null); -// -// // then -// assertThat(result.getErrors()).hasSize(1) -// .element(0) -// .extracting(BidderError::getType) -// .isEqualTo(BidderError.Type.bad_server_response); -// } -// -// @Test -// public void makeBidsShouldReturnBids() throws Exception { -// // given -// final BidRequest bidRequest = BidRequest.builder().build(); -// final BidResponse bidResponse = BidResponse.builder() -// .seatbid(Collections.singletonList(SeatBid.builder() -// .bid(Collections.singletonList(com.iab.openrtb.response.Bid.builder().mtype(1).build())) -// .build())) -// .build(); -// -// final String bidResponseBody = mapper.writeValueAsString(bidResponse); -// final HttpResponse httpResponse = HttpResponse.of(200, null, bidResponseBody); -// final BidderCall bidderCall = BidderCall.succeededHttp(null, httpResponse, null); -// -// // when -// final Result> result = target.makeBids(bidderCall, bidRequest); -// -// // then -// assertThat(result.getErrors()).isEmpty(); -// assertThat(result.getValue()).hasSize(1); -// } - private static BidRequest givenBidRequest() { - final Imp imp = Imp.builder() - .id("imp_id") + final Imp imp = Imp.builder().id("imp_id") .tagid("TagId") // Dodaj tagid .bidfloor(BigDecimal.valueOf(0.5)) // Dodaj bidfloor .ext(mapper.valueToTree(ExtPrebid.of(null, @@ -427,17 +299,8 @@ private static Imp givenImp(Function impCustomiz } private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { - return BidderCall.succeededHttp( - HttpRequest.builder().payload(bidRequest).build(), - HttpResponse.of(200, null, body), - null); - } - - private static BidResponse givenBidResponse(Function bidCustomizer) { - return BidResponse.builder() - .seatbid(singletonList(SeatBid.builder().bid(singletonList(bidCustomizer.apply(Bid.builder()).build())) - .build())) - .build(); + return BidderCall.succeededHttp(HttpRequest.builder() + .payload(bidRequest).build(), HttpResponse.of(200, null, body), null); } private static String givenBidResponse(Bid... bids) throws JsonProcessingException { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json index 7e8d0f14464..64e038dc7c6 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json @@ -3,11 +3,11 @@ "imp": [ { "id": "imp_id", - "secure": 1, "banner": { "w": 300, "h": 250 }, + "secure": 1, "ext": { "tid": "${json-unit.any-string}", "bidder": { From 98588a1170a327f679da961b15cb137ee76026cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Wed, 22 May 2024 16:01:49 +0200 Subject: [PATCH 09/18] New adapter: ReadPeak --- .../bidder/readpeak/ReadPeakBidder.java | 32 ++++++------------- .../test-auction-readpeak-response.json | 7 ++-- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index e3b366b3a9a..54a2cc4a9a0 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -1,7 +1,6 @@ package org.prebid.server.bidder.readpeak; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; @@ -30,7 +29,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Optional; public class ReadPeakBidder implements Bidder { @@ -140,29 +138,17 @@ private static Bid resolveMacros(Bid bid) { .build(); } - private BidType getBidType(Bid bid) { - final JsonNode typeNode = Optional.ofNullable(bid.getExt()) - .map(extNode -> extNode.get("prebid")) - .map(extPrebidNode -> extPrebidNode.get("type")) - .orElse(null); - - final BidType bidType; - try { - bidType = mapper.mapper().convertValue(typeNode, BidType.class); - } catch (IllegalArgumentException e) { - throw new PreBidException("Failed to parse bid.ext.prebid.type for bid.id: '%s'" - .formatted(bid.getId())); - } - - if (bidType == null) { - throw new PreBidException("bid.ext.prebid.type is not present for bid.id: '%s'" - .formatted(bid.getId())); + 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 (bidType) { - case banner, xNative -> bidType; - default -> throw new PreBidException("Unsupported BidType: " - + bidType.getName() + " for bid.id: '" + bid.getId() + "'"); + return switch (markupType) { + case 1 -> BidType.banner; + case 4 -> BidType.xNative; + default -> throw new PreBidException( + "Unable to fetch mediaType " + bid.getMtype() + " in multi-format: " + bid.getImpid()); }; } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json index 25774577057..101455149d7 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-response.json @@ -10,10 +10,11 @@ "crid": "creativeId", "mtype": 1, "ext": { - "origbidcpm": 3.33, "prebid": { - "type": "banner" - } + "type": "banner", + "meta": {} + }, + "origbidcpm": 3.33 } } ], From 51a5b6dcb25a005bdb4bef0b9599adbead185d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Thu, 23 May 2024 10:48:35 +0200 Subject: [PATCH 10/18] New adapter: ReadPeak --- .../bidder/readpeak/ReadPeakBidder.java | 3 +- .../bidder/readpeak/ReadPeakBidderTest.java | 343 ++++++++++-------- 2 files changed, 192 insertions(+), 154 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index 54a2cc4a9a0..d1a232559e3 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -27,6 +27,7 @@ import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -107,7 +108,7 @@ public Result> makeBids(BidderCall httpCall, BidRequ private List extractBids(BidResponse bidResponse) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { - throw new PreBidException("Empty SeatBid array"); + return Collections.emptyList(); } return bidsFromResponse(bidResponse); } diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index 856fa220626..b95f114e79d 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -4,12 +4,11 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; -import io.vertx.core.http.HttpMethod; import org.junit.Test; import org.prebid.server.VertxTest; import org.prebid.server.bidder.model.BidderBid; @@ -20,24 +19,19 @@ import org.prebid.server.bidder.model.Result; import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.readpeak.ExtImpReadPeak; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; +import java.io.IOException; import java.math.BigDecimal; -import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Set; -import java.util.function.Function; import java.util.function.UnaryOperator; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.prebid.server.bidder.model.BidderError.badServerResponse; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; -import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER; -import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE; -import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER; -import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; public class ReadPeakBidderTest extends VertxTest { @@ -51,79 +45,79 @@ public void creationShouldFailOnInvalidEndpointUrl() { } @Test - public void makeHttpRequestsShouldReturnExpectedBody() { - // given - final BidRequest bidRequest = givenBidRequest(); - - // when - final Result>> results = target.makeHttpRequests(bidRequest); - - // then - assertThat(results.getValue()) - .hasSize(1) - .first().satisfies(request -> assertThat(request.getBody()) - .isEqualTo(jacksonMapper.encodeToBytes(bidRequest))) - .satisfies(request -> assertThat(request.getPayload()).isEqualTo(bidRequest)); - assertThat(results.getErrors()).isEmpty(); - } - - @Test - public void makeHttpRequestsShouldReturnExpectedImpIds() { - // given - final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + public void makeHttpRequestsShouldMakeOneRequestPerImp2() { + //given + final BidRequest bidRequest = BidRequest.builder() + .imp(asList(givenImp(UnaryOperator.identity()), givenImp(UnaryOperator.identity()))) + .build(); - // when - final Result>> results = target.makeHttpRequests(bidRequest); + //when + final Result>> result = target.makeHttpRequests(bidRequest); - // then - assertThat(results.getValue()).hasSize(1).first() - .satisfies(request -> assertThat(request.getImpIds()).isEqualTo(Set.of("123"))); - assertThat(results.getErrors()).isEmpty(); + //then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(2) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(List::size) + .containsOnly(1); } @Test - public void makeHttpRequestsShouldReturnExpectedHeaders() { + public void shouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot2() { // given - final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + final BidRequest bidRequest = BidRequest.builder() + .imp(asList(givenImp(UnaryOperator.identity()), givenBadImp(UnaryOperator.identity()))) + .build(); - // when + //when final Result>> result = target.makeHttpRequests(bidRequest); - // then - assertThat(result.getValue()) - .extracting(HttpRequest::getHeaders) - .allSatisfy(headers -> { - assertThat(headers.get(CONTENT_TYPE_HEADER)).isEqualTo(APPLICATION_JSON_CONTENT_TYPE); - assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE); - }); + //then assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(2) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(List::size) + .containsOnly(1); } @Test - public void makeHttpRequestsShouldReturnErrorWhenRequestHasOnlyInvalidImpression() { + public void makeHttpRequestsShouldReturnErrorIfImpExtCannotBeParsed2() { // given - final ObjectNode invalidExt = mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())); - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(invalidExt)); + final BidRequest bidRequest = givenBidRequest(impBuilder -> + impBuilder.ext(mapper.createObjectNode().set("bidder", mapper.createArrayNode()))); // when final Result>> result = target.makeHttpRequests(bidRequest); // then assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> { - assertThat(error.getMessage()).startsWith("Invalid Imp ext: "); - assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); - } - ); + + final List errors = result.getErrors(); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMessage()) + .startsWith("found no valid impressions"); } @Test - public void shouldReplacePlacementIdMacro() { + public void makeHttpRequestsShouldUseBidFloorFromImpIfValid() throws IOException { // given - final BidRequest bidRequest = - givenBidRequest(impBuilder -> impBuilder - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpReadPeak - .of("placement123", null, null, null))))); + final BigDecimal validBidFloor = new BigDecimal("1.23"); + final String bidFloorCurrency = "USD"; + + final Imp imp = Imp.builder() + .id("123") + .banner(Banner.builder().build()) + .bidfloor(validBidFloor) + .bidfloorcur(bidFloorCurrency) + .ext(mapper.valueToTree(ExtPrebid + .of(null, ExtImpReadPeak.of("publisherId", "siteid", BigDecimal.TEN, "tagid")))) + .build(); + + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(imp)) + .build(); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -131,185 +125,228 @@ public void shouldReplacePlacementIdMacro() { // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1); - assertThat(result.getValue()).extracting(HttpRequest::getUri) - .containsExactly("https://test.com/test?param=placement123"); - } - @Test - public void makeBidsShouldReturnErrorWhenResponseCanNotBeParsed() { - // given - final BidderCall httpCall = givenHttpCall(null, "invalid"); - - // when - final Result> actual = target.makeBids(httpCall, null); + final byte[] requestBody = result.getValue().get(0).getBody(); + final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); + final Imp capturedImp = capturedBidRequest.getImp().get(0); - // then - assertThat(actual.getValue()).isEmpty(); - assertThat(actual.getErrors()).allSatisfy(error -> { - assertThat(error.getMessage()).startsWith("Failed to decode"); - assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); - }); + assertThat(capturedImp.getBidfloor()).isEqualByComparingTo(validBidFloor); } @Test - public void makeBidsShouldReturnErrorWhenResponseDoesNotHaveSeatBid() throws JsonProcessingException { + public void makeBidsShouldReturnEmptyListIfBidResponseIsNull2() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, givenBidResponse()); + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(null)); // when - final Result> actual = target.makeBids(httpCall, null); + final Result> result = target.makeBids(httpCall, null); // then - assertThat(actual.getValue()).isEmpty(); - assertThat(actual.getErrors()).containsExactly(badServerResponse("Empty SeatBid array")); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); } @Test - public void makeBidsShouldHandleEmptySeatbid() { + public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed2() { // given - final HttpRequest httpRequest = HttpRequest.builder().method(HttpMethod.POST).uri("https://test.com").body(new byte[0]).build(); - final HttpResponse httpResponse = HttpResponse.of(200, null, "{\"seatbid\": []}"); - final BidderCall httpCall = BidderCall.storedHttp(httpRequest, httpResponse); + final BidderCall httpCall = givenHttpCall(null, "invalid"); // when final Result> result = target.makeBids(httpCall, null); // then - assertThat(result.getErrors()).isNotEmpty(); - assertThat(result.getErrors().get(0).getMessage()).isEqualTo("Empty SeatBid array"); + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token 'invalid':"); + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + }); } @Test - public void makeBidsShouldReturnAllFourBidTypesSuccessfully() throws JsonProcessingException { + public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull2() throws JsonProcessingException { // given - final Bid bannerBid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN).build(); - final Bid nativeBid = Bid.builder().impid("2").mtype(2).price(BigDecimal.TEN).build(); - - final BidderCall httpCall = givenHttpCall(givenBidRequest(), - givenBidResponse(bannerBid, nativeBid)); + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(BidResponse.builder().build())); // when final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsExactly(BidderBid.of(bannerBid, banner, "USD"), BidderBid.of(nativeBid, xNative, "USD")); + assertThat(result.getValue()).isEmpty(); } @Test - public void makeBidsShouldReturnBannerBidSuccessfully() throws JsonProcessingException { + public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { // given - final Bid bannerBid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN).build(); - - final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bannerBid)); - + final ObjectNode bidExt = mapper.createObjectNode() + .putPOJO("prebid", ExtBidPrebid.builder().type(banner).build()); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(1)))); // when final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(bannerBid, banner, "USD")); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(banner); } @Test - public void makeBidsShouldReturnNativeBidSuccessfully() throws JsonProcessingException { + public void makeBidsShouldReturnNativeBid() throws JsonProcessingException { // given - final Bid nativeBid = Bid.builder().impid("2").mtype(2).price(BigDecimal.TEN).build(); - - final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(nativeBid)); - + final ObjectNode bidExt = mapper.createObjectNode() + .putPOJO("prebid", ExtBidPrebid.builder().type(xNative).build()); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").xNative(Native.builder().build()).build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(4)))); // when final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).containsOnly(BidderBid.of(nativeBid, xNative, "USD")); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(xNative); } @Test - public void makeBidsShouldReturnErrorWhenImpTypeIsNotSupported() throws JsonProcessingException { + public void makeBidsShouldReturnErrorForUnsupportedMType() throws JsonProcessingException { // given - final Bid nativeBid = Bid.builder().impid("id").mtype(3).price(BigDecimal.TEN).build(); - final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(nativeBid)); + final ObjectNode bidExt = mapper.createObjectNode() + .putPOJO("prebid", ExtBidPrebid.builder().type(banner).build()); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(2)))); // when final Result> result = target.makeBids(httpCall, null); + // then + assertThat(result.getErrors()).isNotEmpty(); assertThat(result.getErrors()) - .containsExactly(BidderError.badServerResponse("Unable to fetch mediaType 3 in multi-format: id")); + .extracting(BidderError::getMessage) + .containsExactly("Unable to fetch mediaType 2 in multi-format: 123"); + assertThat(result.getValue()).isEmpty(); } @Test - public void makeBidsShouldReturnErrorWhenImpTypeIsNull() throws JsonProcessingException { + public void makeBidsShouldReturnErrorForMissingMType() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("id").mtype(null).price(BigDecimal.TEN).build(); - final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bid)); + final ObjectNode bidExt = mapper.createObjectNode() + .putPOJO("prebid", ExtBidPrebid.builder().type(banner).build()); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123")))); // when final Result> result = target.makeBids(httpCall, null); + // then + assertThat(result.getErrors()).isNotEmpty(); assertThat(result.getErrors()) - .containsExactly(BidderError.badServerResponse("Missing MType for bid: " + bid.getId())); + .extracting(BidderError::getMessage) + .containsExactly("Missing MType for bid: 123"); + assertThat(result.getValue()).isEmpty(); } @Test - public void makeBidsShouldResolveMacrosInBids() throws JsonProcessingException { + public void makeBidsShouldCreateBidExtIfMissing() throws JsonProcessingException { // given - final Bid bid = Bid.builder().impid("1").mtype(1).price(BigDecimal.TEN) - .nurl("http://example.com?price=${AUCTION_PRICE}") - .adm("
${AUCTION_PRICE}
") - .burl("http://example.com?price=${AUCTION_PRICE}") + final Bid bid = Bid.builder() + .id("1") + .price(BigDecimal.valueOf(1.23)) + .adm("${AUCTION_PRICE}") + .nurl("${AUCTION_PRICE}") + .impid("123") + .mtype(1) + .build(); + + final BidResponse bidResponse = BidResponse.builder() + .cur("USD") + .seatbid(List.of(SeatBid.builder() + .bid(List.of(bid)) + .build())) .build(); - final BidderCall httpCall = givenHttpCall(givenBidRequest(), givenBidResponse(bid)); + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) + .build(), + mapper.writeValueAsString(bidResponse)); // when final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - final Bid resolvedBid = result.getValue().get(0).getBid(); - assertThat(resolvedBid.getNurl()).isEqualTo("http://example.com?price=10"); - assertThat(resolvedBid.getAdm()).isEqualTo("
10
"); - assertThat(resolvedBid.getBurl()).isEqualTo("http://example.com?price=10"); - } + assertThat(result.getValue()).hasSize(1); - private static BidRequest givenBidRequest() { - final Imp imp = Imp.builder().id("imp_id") - .tagid("TagId") // Dodaj tagid - .bidfloor(BigDecimal.valueOf(0.5)) // Dodaj bidfloor - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpReadPeak - .of("PublisherId", "SiteId", BigDecimal.valueOf(0.5), "TagId")))).build(); - return BidRequest.builder().imp(List.of(imp)).build(); + final BidderBid bidderBid = result.getValue().get(0); + final Bid updatedBid = bidderBid.getBid(); + + assertThat(updatedBid.getExt()).isNotNull(); + assertThat(updatedBid.getExt().has("prebid")).isTrue(); + assertThat(updatedBid.getExt().get("prebid").has("meta")).isTrue(); } - private static BidRequest givenBidRequest(UnaryOperator... impCustomizers) { - return BidRequest.builder() - .device(Device.builder().ua("ua").ip("ip").ipv6("ipv6").build()) - .imp(Arrays.stream(impCustomizers).map(ReadPeakBidderTest::givenImp).toList()) + private static BidRequest givenBidRequest( + UnaryOperator bidRequestCustomizer, + UnaryOperator impCustomizer) { + + return bidRequestCustomizer.apply(BidRequest.builder() + .imp(List.of(givenImp(impCustomizer)))) .build(); } - private static Imp givenImp(Function impCustomizer) { - return impCustomizer.apply(Imp.builder().id("123").banner(Banner.builder().w(23).h(25).build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpReadPeak - .of("publisherId", null, null, null))))).build(); + private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { + return givenBidRequest(UnaryOperator.identity(), impCustomizer); } - private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { - return BidderCall.succeededHttp(HttpRequest.builder() - .payload(bidRequest).build(), HttpResponse.of(200, null, body), null); + private static Imp givenImp(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpReadPeak.of("publisherId", "siteId", BigDecimal.valueOf(1.23), "someTagId")))) + .build().toBuilder()).build(); + } + + private static Imp givenBadImp(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("invalidImp") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpReadPeak.of(null, null, null, null)))) + .build().toBuilder()).build(); } - private static String givenBidResponse(Bid... bids) throws JsonProcessingException { - return mapper.writeValueAsString( - BidResponse.builder() - .cur("USD") - .seatbid(bids.length == 0 - ? Collections.emptyList() - : List.of(SeatBid.builder().bid(List.of(bids)).build())) - .build()); + private static BidResponse givenBidResponse(UnaryOperator bidCustomizer) { + return BidResponse.builder() + .cur("USD") + .seatbid(List.of(SeatBid.builder().bid(List.of(bidCustomizer.apply(Bid.builder().id("123")).build())) + .build())) + .build(); + } + + private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { + return BidderCall.succeededHttp( + HttpRequest.builder().payload(bidRequest).build(), + HttpResponse.of(200, null, body), + null); } } From 14a31d7c1af7b6d15499d64ab3cdaf286783fbad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Thu, 23 May 2024 15:25:27 +0200 Subject: [PATCH 11/18] resolve comments --- .../bidder/readpeak/ReadPeakBidder.java | 139 ++++++++++++++---- .../resources/bidder-config/readpeak.yaml | 2 +- .../bidder/readpeak/ReadPeakBidderTest.java | 2 +- .../test-auction-readpeak-request.json | 5 +- .../readpeak/test-readpeak-bid-request.json | 16 +- .../server/it/test-application.properties | 2 +- 6 files changed, 130 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index d1a232559e3..8cc7179ab8d 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -1,13 +1,18 @@ package org.prebid.server.bidder.readpeak; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Publisher; +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 org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; @@ -15,13 +20,14 @@ 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.bidder.model.Price; 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.readpeak.ExtImpReadPeak; 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.util.BidderUtil; import org.prebid.server.util.HttpUtil; @@ -36,6 +42,9 @@ public class ReadPeakBidder implements Bidder { private static final TypeReference> READPEAK_EXT_TYPE_REFERENCE = new TypeReference<>() { }; + private static final TypeReference> EXT_PREBID_TYPE_REFERENCE = + new TypeReference<>() { + }; private static final String PRICE_MACRO = "${AUCTION_PRICE}"; @@ -64,7 +73,9 @@ public Result>> makeHttpRequests(BidRequest request } if (httpRequests.isEmpty()) { - return Result.withError(BidderError.badInput("found no valid impressions")); + return Result.withError(BidderError + .badInput(String.format("Failed to find compatible impressions for request %s", request.getId()))); + } return Result.of(httpRequests, errors); @@ -79,18 +90,39 @@ private ExtImpReadPeak parseImpExt(Imp imp) { } private Imp modifyImp(Imp imp, ExtImpReadPeak extImpReadPeak) { - final Price bidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); - return imp.toBuilder() - .bidfloor(BidderUtil.isValidPrice(bidFloorPrice) - ? bidFloorPrice.getValue() - : extImpReadPeak.getBidFloor()) - .tagid(extImpReadPeak.getTagId()) + .bidfloor(extImpReadPeak.getBidFloor() != null ? extImpReadPeak.getBidFloor() : imp.getBidfloor()) + .tagid(StringUtils.isNotBlank(extImpReadPeak.getTagId()) ? extImpReadPeak.getTagId() : null) .build(); } private HttpRequest makeHttpRequest(BidRequest request, Imp imp) { - final BidRequest outgoingRequest = request.toBuilder().imp(List.of(imp)).build(); + final BidRequest.BidRequestBuilder requestBuilder = request.toBuilder() + .imp(List.of(imp)); + + final ExtImpReadPeak extImpReadPeak = parseImpExt(imp); + + final Publisher publisher = Publisher.builder() + .id(extImpReadPeak.getPublisherId()) + .build(); + + if (request.getSite() != null) { + final Site site = request.getSite().toBuilder() + .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) + ? extImpReadPeak.getSiteId() : request.getSite().getId()) + .publisher(publisher) + .build(); + requestBuilder.site(site); + } else if (request.getApp() != null) { + final App app = request.getApp().toBuilder() + .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) + ? extImpReadPeak.getSiteId() : request.getApp().getId()) + .publisher(publisher) + .build(); + requestBuilder.app(app); + } + + final BidRequest outgoingRequest = requestBuilder.build(); return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); } @@ -119,31 +151,81 @@ private List bidsFromResponse(BidResponse bidResponse) { .map(SeatBid::getBid) .filter(Objects::nonNull) .flatMap(List::stream) - .map(bid -> { - final Bid resolvedBid = resolveMacros(bid); - final BidType bidType = getBidType(bid); - final Bid updatedBid = addBidMeta(resolvedBid); - return BidderBid.of(updatedBid, bidType, bidResponse.getCur()); - }) + .map(bid -> makeBid(bid, bidResponse)) .filter(Objects::nonNull) .toList(); } - private static Bid resolveMacros(Bid bid) { + private BidderBid makeBid(Bid bid, BidResponse bidResponse) { + if (bid.getExt() == null) { + throw new PreBidException("Bid ext is null"); + } + final Bid resolvedBid = resolveMacros(bid); + if (resolvedBid.getExt() == null) { + throw new PreBidException("Bid ext is null"); + } + final BidType bidType = getBidType(resolvedBid); + + // Parse existing ExtBidPrebidMeta + final ExtBidPrebid extBidPrebid = parseExtBidPrebidMeta(resolvedBid); + final ExtBidPrebidMeta meta = extBidPrebid.getMeta(); + + // Validate rendererName and rendererVersion + if (StringUtils.isBlank(meta.getRendererName())) { + throw new PreBidException("RendererName should not be empty"); + } + if (StringUtils.isBlank(meta.getRendererVersion())) { + throw new PreBidException("RendererVersion should not be empty"); + } + + // Build modified ExtBidPrebidMeta + final ExtBidPrebidMeta modifiedMeta = ExtBidPrebidMeta.builder() + .rendererName(meta.getRendererName()) + .rendererVersion(meta.getRendererVersion()) + .advertiserDomains(meta.getAdvertiserDomains()) // add other required fields here + .build(); + + // Build modified ExtBidPrebid + final ExtBidPrebid modifiedPrebid = extBidPrebid.toBuilder() + .meta(modifiedMeta) + .build(); + + // Convert to ObjectNode + final ObjectNode modifiedBidExt = mapper.mapper().valueToTree(ExtPrebid.of(modifiedPrebid, null)); + + // Return BidderBid + return BidderBid.of(resolvedBid.toBuilder().ext(modifiedBidExt).build(), bidType, bidResponse.getCur()); + } + + private ExtBidPrebid parseExtBidPrebidMeta(Bid bid) { + try { + if (bid.getExt() == null) { + throw new PreBidException("Bid ext is null"); + } + final ExtBidPrebid extPrebid = mapper.mapper() + .convertValue(bid.getExt(), EXT_PREBID_TYPE_REFERENCE).getPrebid(); + if (extPrebid == null) { + throw new PreBidException("Failed to parse extPrebid from bid ext"); + } + return extPrebid; + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage()); + } + } + + private Bid resolveMacros(Bid bid) { final BigDecimal price = bid.getPrice(); final String priceAsString = price != null ? price.toPlainString() : "0"; return bid.toBuilder() .nurl(StringUtils.replace(bid.getNurl(), PRICE_MACRO, priceAsString)) .adm(StringUtils.replace(bid.getAdm(), PRICE_MACRO, priceAsString)) + .burl(StringUtils.replace(bid.getAdm(), PRICE_MACRO, priceAsString)) .build(); } - private static BidType getBidType(Bid bid) { - final Integer markupType = bid.getMtype(); - if (markupType == null) { - throw new PreBidException("Missing MType for bid: " + bid.getId()); - } + private BidType getBidType(Bid bid) { + final Integer markupType = ObjectUtils.defaultIfNull(bid.getMtype(), 0); return switch (markupType) { case 1 -> BidType.banner; @@ -154,13 +236,16 @@ private static BidType getBidType(Bid bid) { } private Bid addBidMeta(Bid bid) { - final ObjectNode bidExt = bid.getExt() != null - ? bid.getExt().deepCopy() - : mapper.mapper().createObjectNode(); + // Build the ExtBidPrebidMeta object + final ExtBidPrebidMeta extBidPrebidMeta = ExtBidPrebidMeta.builder() + .advertiserDomains(bid.getAdomain()) + .build(); + + // Convert ExtBidPrebidMeta to ObjectNode + final ObjectNode bidExt = bid.getExt() != null ? bid.getExt().deepCopy() + : JsonNodeFactory.instance.objectNode(); + bidExt.set("prebid", mapper.mapper().valueToTree(extBidPrebidMeta)); - final ObjectNode prebidNode = bidExt.putObject("prebid"); - final ObjectNode metaNode = prebidNode.putObject("meta"); - metaNode.set("advertiserDomains", mapper.mapper().valueToTree(bid.getAdomain())); return bid.toBuilder() .ext(bidExt) .build(); diff --git a/src/main/resources/bidder-config/readpeak.yaml b/src/main/resources/bidder-config/readpeak.yaml index 3ba30730d7d..0982f1f9d17 100644 --- a/src/main/resources/bidder-config/readpeak.yaml +++ b/src/main/resources/bidder-config/readpeak.yaml @@ -1,6 +1,6 @@ adapters: readpeak: - endpoint: https://dsp.readpeak.com/header/prebid?p={{PublisherId}} + endpoint: https://dsp.readpeak.com/header/prebid geoscope: - EEA meta-info: diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index b95f114e79d..16be4eb3e0c 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -35,7 +35,7 @@ public class ReadPeakBidderTest extends VertxTest { - private static final String ENDPOINT_URL = "https://test.com/test?param={{PublisherId}}"; + private static final String ENDPOINT_URL = "https://test.com/test"; private final ReadPeakBidder target = new ReadPeakBidder(ENDPOINT_URL, jacksonMapper); diff --git a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-request.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-request.json index 811388f5487..7fc5c73a49c 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-auction-readpeak-request.json @@ -9,7 +9,10 @@ }, "ext": { "readpeak": { - "publisherId": "testPublisherId" + "siteId": "site-id-b", + "publisherId": "publisher-id-b", + "tagId": "tag-id-b", + "bidfloor": 5.0 } } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json index 64e038dc7c6..025da681ee3 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-request.json @@ -7,23 +7,26 @@ "w": 300, "h": 250 }, + "tagid" : "tag-id-b", + "bidfloor" : 5, "secure": 1, "ext": { "tid": "${json-unit.any-string}", "bidder": { - "publisherId": "testPublisherId" + "siteId": "site-id-b", + "publisherId": "publisher-id-b", + "tagId": "tag-id-b", + "bidfloor": 5.0 } } } ], - "source": { - "tid": "${json-unit.any-string}" - }, "site": { + "id" : "site-id-b", "domain": "www.example.com", "page": "http://www.example.com", "publisher": { - "domain": "example.com" + "id": "publisher-id-b" }, "ext": { "amp": 0 @@ -38,6 +41,9 @@ "cur": [ "USD" ], + "source": { + "tid": "${json-unit.any-string}" + }, "regs": { "ext": { "gdpr": 0 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 b8311511208..7855578446b 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -309,7 +309,7 @@ adapters.pulsepoint.endpoint=http://localhost:8090/pulsepoint-exchange adapters.pwbid.enabled=true adapters.pwbid.endpoint=http://localhost:8090/pwbid-exchange adapters.readpeak.enabled=true -adapters.readpeak.endpoint=http://localhost:8090/readpeak-exchange?pubId={{PublisherId}} +adapters.readpeak.endpoint=http://localhost:8090/readpeak-exchange adapters.relevantdigital.enabled=true adapters.relevantdigital.endpoint=http://localhost:8090/relevantdigital-exchange?pbsHost={{Host}} adapters.resetdigital.enabled=true From 6a62f01bda1bdbff61862e69cf0837b3b13a5b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Thu, 23 May 2024 17:43:34 +0200 Subject: [PATCH 12/18] resolve comments --- .../bidder/readpeak/ReadPeakBidder.java | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index 8cc7179ab8d..ca6e1f064c9 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -58,27 +58,30 @@ public ReadPeakBidder(String endpointUrl, JacksonMapper mapper) { @Override public Result>> makeHttpRequests(BidRequest request) { - final List> httpRequests = new ArrayList<>(); + final List modifiedImps = new ArrayList<>(); + final List extImpReadPeaks = new ArrayList<>(); final List errors = new ArrayList<>(); for (Imp imp : request.getImp()) { - final ExtImpReadPeak extImpReadPeak; try { - extImpReadPeak = parseImpExt(imp); + final ExtImpReadPeak extImpReadPeak = parseImpExt(imp); final Imp modifiedImp = modifyImp(imp, extImpReadPeak); - httpRequests.add(makeHttpRequest(request, modifiedImp)); + modifiedImps.add(modifiedImp); + extImpReadPeaks.add(extImpReadPeak); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); } } - if (httpRequests.isEmpty()) { + if (modifiedImps.isEmpty()) { return Result.withError(BidderError .badInput(String.format("Failed to find compatible impressions for request %s", request.getId()))); - } - return Result.of(httpRequests, errors); + final BidRequest modifiedRequest = request.toBuilder().imp(modifiedImps).build(); + final HttpRequest httpRequest = makeHttpRequest(modifiedRequest, extImpReadPeaks.get(0)); + + return Result.of(Collections.singletonList(httpRequest), errors); } private ExtImpReadPeak parseImpExt(Imp imp) { @@ -96,16 +99,13 @@ private Imp modifyImp(Imp imp, ExtImpReadPeak extImpReadPeak) { .build(); } - private HttpRequest makeHttpRequest(BidRequest request, Imp imp) { - final BidRequest.BidRequestBuilder requestBuilder = request.toBuilder() - .imp(List.of(imp)); - - final ExtImpReadPeak extImpReadPeak = parseImpExt(imp); - + private HttpRequest makeHttpRequest(BidRequest request, ExtImpReadPeak extImpReadPeak) { final Publisher publisher = Publisher.builder() .id(extImpReadPeak.getPublisherId()) .build(); + final BidRequest.BidRequestBuilder requestBuilder = request.toBuilder(); + if (request.getSite() != null) { final Site site = request.getSite().toBuilder() .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) @@ -156,13 +156,13 @@ private List bidsFromResponse(BidResponse bidResponse) { .toList(); } - private BidderBid makeBid(Bid bid, BidResponse bidResponse) { - if (bid.getExt() == null) { - throw new PreBidException("Bid ext is null"); + private BidderBid makeBid2(Bid bid, BidResponse bidResponse) { + if (bid.getExt() == null) { // only to test where is null + throw new PreBidException("Bid ext is null"); // only to test where is null } final Bid resolvedBid = resolveMacros(bid); - if (resolvedBid.getExt() == null) { - throw new PreBidException("Bid ext is null"); + if (resolvedBid.getExt() == null) { // only to test where is null + throw new PreBidException("Bid ext is null"); // only to test where is null } final BidType bidType = getBidType(resolvedBid); @@ -170,14 +170,6 @@ private BidderBid makeBid(Bid bid, BidResponse bidResponse) { final ExtBidPrebid extBidPrebid = parseExtBidPrebidMeta(resolvedBid); final ExtBidPrebidMeta meta = extBidPrebid.getMeta(); - // Validate rendererName and rendererVersion - if (StringUtils.isBlank(meta.getRendererName())) { - throw new PreBidException("RendererName should not be empty"); - } - if (StringUtils.isBlank(meta.getRendererVersion())) { - throw new PreBidException("RendererVersion should not be empty"); - } - // Build modified ExtBidPrebidMeta final ExtBidPrebidMeta modifiedMeta = ExtBidPrebidMeta.builder() .rendererName(meta.getRendererName()) @@ -213,6 +205,13 @@ private ExtBidPrebid parseExtBidPrebidMeta(Bid bid) { } } + private BidderBid makeBid(Bid bid, BidResponse bidResponse) { + final Bid resolvedBid = resolveMacros(bid); + final BidType bidType = getBidType(bid); + final Bid updatedBid = addBidMeta(resolvedBid); + return BidderBid.of(updatedBid, bidType, bidResponse.getCur()); + } + private Bid resolveMacros(Bid bid) { final BigDecimal price = bid.getPrice(); final String priceAsString = price != null ? price.toPlainString() : "0"; @@ -220,7 +219,7 @@ private Bid resolveMacros(Bid bid) { return bid.toBuilder() .nurl(StringUtils.replace(bid.getNurl(), PRICE_MACRO, priceAsString)) .adm(StringUtils.replace(bid.getAdm(), PRICE_MACRO, priceAsString)) - .burl(StringUtils.replace(bid.getAdm(), PRICE_MACRO, priceAsString)) + .burl(StringUtils.replace(bid.getBurl(), PRICE_MACRO, priceAsString)) .build(); } From 12ec3c74eeecfeebde6a543e39a8396ae4f28a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Thu, 23 May 2024 17:58:04 +0200 Subject: [PATCH 13/18] resolve comments --- .../bidder/readpeak/ReadPeakBidder.java | 56 ++----------------- .../readpeak/test-readpeak-bid-response.json | 12 +++- 2 files changed, 15 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index ca6e1f064c9..857a17b0d0a 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -156,55 +156,6 @@ private List bidsFromResponse(BidResponse bidResponse) { .toList(); } - private BidderBid makeBid2(Bid bid, BidResponse bidResponse) { - if (bid.getExt() == null) { // only to test where is null - throw new PreBidException("Bid ext is null"); // only to test where is null - } - final Bid resolvedBid = resolveMacros(bid); - if (resolvedBid.getExt() == null) { // only to test where is null - throw new PreBidException("Bid ext is null"); // only to test where is null - } - final BidType bidType = getBidType(resolvedBid); - - // Parse existing ExtBidPrebidMeta - final ExtBidPrebid extBidPrebid = parseExtBidPrebidMeta(resolvedBid); - final ExtBidPrebidMeta meta = extBidPrebid.getMeta(); - - // Build modified ExtBidPrebidMeta - final ExtBidPrebidMeta modifiedMeta = ExtBidPrebidMeta.builder() - .rendererName(meta.getRendererName()) - .rendererVersion(meta.getRendererVersion()) - .advertiserDomains(meta.getAdvertiserDomains()) // add other required fields here - .build(); - - // Build modified ExtBidPrebid - final ExtBidPrebid modifiedPrebid = extBidPrebid.toBuilder() - .meta(modifiedMeta) - .build(); - - // Convert to ObjectNode - final ObjectNode modifiedBidExt = mapper.mapper().valueToTree(ExtPrebid.of(modifiedPrebid, null)); - - // Return BidderBid - return BidderBid.of(resolvedBid.toBuilder().ext(modifiedBidExt).build(), bidType, bidResponse.getCur()); - } - - private ExtBidPrebid parseExtBidPrebidMeta(Bid bid) { - try { - if (bid.getExt() == null) { - throw new PreBidException("Bid ext is null"); - } - final ExtBidPrebid extPrebid = mapper.mapper() - .convertValue(bid.getExt(), EXT_PREBID_TYPE_REFERENCE).getPrebid(); - if (extPrebid == null) { - throw new PreBidException("Failed to parse extPrebid from bid ext"); - } - return extPrebid; - } catch (IllegalArgumentException e) { - throw new PreBidException(e.getMessage()); - } - } - private BidderBid makeBid(Bid bid, BidResponse bidResponse) { final Bid resolvedBid = resolveMacros(bid); final BidType bidType = getBidType(bid); @@ -235,15 +186,16 @@ private BidType getBidType(Bid bid) { } private Bid addBidMeta(Bid bid) { - // Build the ExtBidPrebidMeta object final ExtBidPrebidMeta extBidPrebidMeta = ExtBidPrebidMeta.builder() .advertiserDomains(bid.getAdomain()) .build(); - // Convert ExtBidPrebidMeta to ObjectNode final ObjectNode bidExt = bid.getExt() != null ? bid.getExt().deepCopy() : JsonNodeFactory.instance.objectNode(); - bidExt.set("prebid", mapper.mapper().valueToTree(extBidPrebidMeta)); + final ObjectNode prebidNode = bidExt + .has("prebid") ? (ObjectNode) bidExt.get("prebid") : JsonNodeFactory.instance.objectNode(); + prebidNode.set("meta", mapper.mapper().valueToTree(extBidPrebidMeta)); + bidExt.set("prebid", prebidNode); return bid.toBuilder() .ext(bidExt) diff --git a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-response.json index 6922c116b46..d15e64a2bb4 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/readpeak/test-readpeak-bid-response.json @@ -8,7 +8,17 @@ "impid": "imp_id", "price": 3.33, "crid": "creativeId", - "mtype": 1 + "mtype": 1, + "ext": { + "prebid": { + "type": "banner", + "meta": { + "advertiserDomains": [ + "example.com" + ] + } + } + } } ] } From 61c46048d4d7456cbd4a7532b8524e7f75878bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Fri, 24 May 2024 08:42:49 +0200 Subject: [PATCH 14/18] resolve comments --- .../bidder/readpeak/ReadPeakBidderTest.java | 141 ++++++++++++++---- 1 file changed, 116 insertions(+), 25 deletions(-) diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index 16be4eb3e0c..bdd80e5a459 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -2,10 +2,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.Site; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; @@ -23,6 +25,7 @@ import java.io.IOException; import java.math.BigDecimal; +import java.util.Collections; import java.util.List; import java.util.function.UnaryOperator; @@ -45,41 +48,49 @@ public void creationShouldFailOnInvalidEndpointUrl() { } @Test - public void makeHttpRequestsShouldMakeOneRequestPerImp2() { - //given + public void makeHttpRequestsShouldContainAllImpsInOneRequest() throws IOException { + // given + final Imp imp1 = givenImp(imp -> imp.id("imp1")); + final Imp imp2 = givenImp(imp -> imp.id("imp2")); final BidRequest bidRequest = BidRequest.builder() - .imp(asList(givenImp(UnaryOperator.identity()), givenImp(UnaryOperator.identity()))) + .imp(asList(imp1, imp2)) .build(); - //when + // when final Result>> result = target.makeHttpRequests(bidRequest); - //then + // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(2) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getImp) - .extracting(List::size) - .containsOnly(1); + assertThat(result.getValue()).hasSize(1); + + final byte[] requestBody = result.getValue().get(0).getBody(); + final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); + + assertThat(capturedBidRequest.getImp()).hasSize(2); + assertThat(capturedBidRequest.getImp()).extracting(Imp::getId).containsExactly("imp1", "imp2"); } @Test - public void shouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot2() { + public void shouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot() throws IOException { // given + final Imp validImp = givenImp(imp -> imp.id("validImp")); + final Imp invalidImp = givenBadImp(imp -> imp.id("invalidImp")); final BidRequest bidRequest = BidRequest.builder() - .imp(asList(givenImp(UnaryOperator.identity()), givenBadImp(UnaryOperator.identity()))) + .imp(asList(validImp, invalidImp)) .build(); - //when + // when final Result>> result = target.makeHttpRequests(bidRequest); - //then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(2) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getImp) - .extracting(List::size) - .containsOnly(1); + // then + assertThat(result.getErrors()).hasSize(1); // Oczekujemy jednego błędu dla niepoprawnej impresji + assertThat(result.getValue()).hasSize(1); // Oczekujemy jednego żądania HTTP + + final byte[] requestBody = result.getValue().get(0).getBody(); + final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); + + assertThat(capturedBidRequest.getImp()).hasSize(1); + assertThat(capturedBidRequest.getImp()).extracting(Imp::getId).containsExactly("validImp"); } @Test @@ -97,7 +108,7 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCannotBeParsed2() { final List errors = result.getErrors(); assertThat(errors).hasSize(1); assertThat(errors.get(0).getMessage()) - .startsWith("found no valid impressions"); + .startsWith("Failed to find compatible impressions for request"); } @Test @@ -112,7 +123,7 @@ public void makeHttpRequestsShouldUseBidFloorFromImpIfValid() throws IOException .bidfloor(validBidFloor) .bidfloorcur(bidFloorCurrency) .ext(mapper.valueToTree(ExtPrebid - .of(null, ExtImpReadPeak.of("publisherId", "siteid", BigDecimal.TEN, "tagid")))) + .of(null, ExtImpReadPeak.of("publisherId", "siteid", null, "tagid")))) .build(); final BidRequest bidRequest = BidRequest.builder() @@ -133,6 +144,79 @@ public void makeHttpRequestsShouldUseBidFloorFromImpIfValid() throws IOException assertThat(capturedImp.getBidfloor()).isEqualByComparingTo(validBidFloor); } + @Test + public void makeHttpRequestsShouldSetSitePublisher() throws IOException { + // given + final Site site = Site.builder().id("siteId").build(); + final Imp validImp = givenImp(imp -> imp.id("validImp")); + final BidRequest bidRequest = BidRequest.builder() + .site(site) + .imp(Collections.singletonList(validImp)) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + + final byte[] requestBody = result.getValue().get(0).getBody(); + final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); + assertThat(capturedBidRequest.getSite()).isNotNull(); + assertThat(capturedBidRequest.getSite().getPublisher()).isNotNull(); + assertThat(capturedBidRequest.getSite().getPublisher().getId()).isEqualTo("publisherId"); + assertThat(capturedBidRequest.getSite().getId()).isEqualTo("siteId"); + } + + @Test + public void makeHttpRequestsShouldSetAppPublisher() throws IOException { + // given + final App app = App.builder().id("appId").build(); + final Imp validImp = givenImpWithNullSideId(imp -> imp.id("validImp")); + final BidRequest bidRequest = BidRequest.builder() + .app(app) + .imp(Collections.singletonList(validImp)) + .build(); + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + + final byte[] requestBody = result.getValue().get(0).getBody(); + final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); + assertThat(capturedBidRequest.getApp()).isNotNull(); + assertThat(capturedBidRequest.getApp().getPublisher()).isNotNull(); + assertThat(capturedBidRequest.getApp().getPublisher().getId()).isEqualTo("publisherId"); + assertThat(capturedBidRequest.getApp().getId()).isEqualTo("appId"); + } + + @Test + public void makeHttpRequestsShouldUseSiteIdForAppWhenSiteIdIsNotBlank() throws IOException { + // given + final App app = App.builder().id("appId").build(); + final Imp validImp = givenImp(imp -> imp.id("validImp")); + final BidRequest bidRequest = BidRequest.builder() + .app(app) + .imp(Collections.singletonList(validImp)) + .build(); + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + + final byte[] requestBody = result.getValue().get(0).getBody(); + final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); + assertThat(capturedBidRequest.getApp()).isNotNull(); + assertThat(capturedBidRequest.getApp().getPublisher()).isNotNull(); + assertThat(capturedBidRequest.getApp().getPublisher().getId()).isEqualTo("publisherId"); + assertThat(capturedBidRequest.getApp().getId()).isEqualTo("siteId"); + } + @Test public void makeBidsShouldReturnEmptyListIfBidResponseIsNull2() throws JsonProcessingException { // given @@ -262,7 +346,7 @@ public void makeBidsShouldReturnErrorForMissingMType() throws JsonProcessingExce assertThat(result.getErrors()).isNotEmpty(); assertThat(result.getErrors()) .extracting(BidderError::getMessage) - .containsExactly("Missing MType for bid: 123"); + .containsExactly("Unable to fetch mediaType null in multi-format: 123"); assertThat(result.getValue()).isEmpty(); } @@ -327,11 +411,18 @@ private static Imp givenImp(UnaryOperator impCustomizer) { .build().toBuilder()).build(); } + private static Imp givenImpWithNullSideId(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpReadPeak.of("publisherId", null, BigDecimal.valueOf(1.23), "someTagId")))) + .build().toBuilder()).build(); + } + private static Imp givenBadImp(UnaryOperator impCustomizer) { return impCustomizer.apply(Imp.builder() .id("invalidImp") - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpReadPeak.of(null, null, null, null)))) + .ext(mapper.createObjectNode().put("bidder", "invalidValue")) // nieprawidłowa wartość powodująca błąd parsowania .build().toBuilder()).build(); } From 32f5be402bc872e836dec5fc7185fd1bc2c5bcb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Fri, 7 Jun 2024 14:46:07 +0200 Subject: [PATCH 15/18] resolve comments --- .../bidder/readpeak/ReadPeakBidder.java | 102 +++++++++++------- .../bidder/readpeak/ReadPeakBidderTest.java | 10 +- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index 857a17b0d0a..7a244b628a4 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -1,7 +1,6 @@ package org.prebid.server.bidder.readpeak; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; @@ -42,9 +41,6 @@ public class ReadPeakBidder implements Bidder { private static final TypeReference> READPEAK_EXT_TYPE_REFERENCE = new TypeReference<>() { }; - private static final TypeReference> EXT_PREBID_TYPE_REFERENCE = - new TypeReference<>() { - }; private static final String PRICE_MACRO = "${AUCTION_PRICE}"; @@ -95,7 +91,7 @@ private ExtImpReadPeak parseImpExt(Imp imp) { private Imp modifyImp(Imp imp, ExtImpReadPeak extImpReadPeak) { return imp.toBuilder() .bidfloor(extImpReadPeak.getBidFloor() != null ? extImpReadPeak.getBidFloor() : imp.getBidfloor()) - .tagid(StringUtils.isNotBlank(extImpReadPeak.getTagId()) ? extImpReadPeak.getTagId() : null) + .tagid(StringUtils.isNotBlank(extImpReadPeak.getTagId()) ? extImpReadPeak.getTagId() : imp.getTagid()) .build(); } @@ -106,54 +102,74 @@ private HttpRequest makeHttpRequest(BidRequest request, ExtImpReadPe final BidRequest.BidRequestBuilder requestBuilder = request.toBuilder(); - if (request.getSite() != null) { - final Site site = request.getSite().toBuilder() - .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) - ? extImpReadPeak.getSiteId() : request.getSite().getId()) - .publisher(publisher) - .build(); - requestBuilder.site(site); - } else if (request.getApp() != null) { - final App app = request.getApp().toBuilder() - .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) - ? extImpReadPeak.getSiteId() : request.getApp().getId()) - .publisher(publisher) - .build(); - requestBuilder.app(app); - } + final boolean hasSite = request.getSite() != null; + final boolean hasApp = !hasSite && request.getApp() != null; - final BidRequest outgoingRequest = requestBuilder.build(); + final BidRequest outgoingRequest = requestBuilder + .site(hasSite ? modifySite(request.getSite(), extImpReadPeak, publisher) : request.getSite()) + .app(hasApp ? modifyApp(request.getApp(), extImpReadPeak, publisher) : request.getApp()) + .build(); return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); } + private Site modifySite(Site site, ExtImpReadPeak extImpReadPeak, Publisher publisher) { + return site.toBuilder() + .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) + ? extImpReadPeak.getSiteId() : site.getId()) + .publisher(publisher) + .build(); + } + + private App modifyApp(App app, ExtImpReadPeak extImpReadPeak, Publisher publisher) { + return app.toBuilder() + .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) + ? extImpReadPeak.getSiteId() : app.getId()) + .publisher(publisher) + .build(); + } + @Override public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - final List bids = extractBids(bidResponse); - return Result.withValues(bids); + return extractBids(bidResponse); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private List extractBids(BidResponse bidResponse) { + private Result> extractBids(BidResponse bidResponse) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { - return Collections.emptyList(); + return Result.withValues(Collections.emptyList()); } return bidsFromResponse(bidResponse); } - private List bidsFromResponse(BidResponse bidResponse) { - return bidResponse.getSeatbid().stream() - .filter(Objects::nonNull) - .map(SeatBid::getBid) - .filter(Objects::nonNull) - .flatMap(List::stream) - .map(bid -> makeBid(bid, bidResponse)) - .filter(Objects::nonNull) - .toList(); + private Result> bidsFromResponse(BidResponse bidResponse) { + final List errors = new ArrayList<>(); + final List validBids = new ArrayList<>(); + + for (SeatBid seatBid : bidResponse.getSeatbid()) { + if (seatBid != null) { + for (Bid bid : seatBid.getBid()) { + if (bid != null) { + try { + final BidderBid bidderBid = makeBid(bid, bidResponse); + validBids.add(bidderBid); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + } + } + } + + if (!errors.isEmpty()) { + return Result.withErrors(errors); + } + + return Result.withValues(validBids); } private BidderBid makeBid(Bid bid, BidResponse bidResponse) { @@ -190,15 +206,19 @@ private Bid addBidMeta(Bid bid) { .advertiserDomains(bid.getAdomain()) .build(); - final ObjectNode bidExt = bid.getExt() != null ? bid.getExt().deepCopy() - : JsonNodeFactory.instance.objectNode(); - final ObjectNode prebidNode = bidExt - .has("prebid") ? (ObjectNode) bidExt.get("prebid") : JsonNodeFactory.instance.objectNode(); - prebidNode.set("meta", mapper.mapper().valueToTree(extBidPrebidMeta)); - bidExt.set("prebid", prebidNode); + final ExtBidPrebidMeta modifiedMeta = ExtBidPrebidMeta.builder() + .advertiserDomains(bid.getAdomain()) + .build(); + + final ExtBidPrebid modifiedPrebid = ExtBidPrebid.builder() + .meta(modifiedMeta) + .build(); + + final ObjectNode modifiedBidExt = mapper.mapper() + .valueToTree(ExtPrebid.of(modifiedPrebid, null)); return bid.toBuilder() - .ext(bidExt) + .ext(modifiedBidExt) .build(); } } diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index bdd80e5a459..787635a4dc4 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -71,7 +71,7 @@ public void makeHttpRequestsShouldContainAllImpsInOneRequest() throws IOExceptio } @Test - public void shouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot() throws IOException { + public void makeHttpRequestsShouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot() throws IOException { // given final Imp validImp = givenImp(imp -> imp.id("validImp")); final Imp invalidImp = givenBadImp(imp -> imp.id("invalidImp")); @@ -94,7 +94,7 @@ public void shouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot() throws IOExce } @Test - public void makeHttpRequestsShouldReturnErrorIfImpExtCannotBeParsed2() { + public void makeHttpRequestsShouldReturnErrorIfImpExtCannotBeParsed() { // given final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(mapper.createObjectNode().set("bidder", mapper.createArrayNode()))); @@ -173,7 +173,7 @@ public void makeHttpRequestsShouldSetSitePublisher() throws IOException { public void makeHttpRequestsShouldSetAppPublisher() throws IOException { // given final App app = App.builder().id("appId").build(); - final Imp validImp = givenImpWithNullSideId(imp -> imp.id("validImp")); + final Imp validImp = givenImpWithoutSiteId(imp -> imp.id("validImp")); final BidRequest bidRequest = BidRequest.builder() .app(app) .imp(Collections.singletonList(validImp)) @@ -320,7 +320,7 @@ public void makeBidsShouldReturnErrorForUnsupportedMType() throws JsonProcessing final Result> result = target.makeBids(httpCall, null); // then - assertThat(result.getErrors()).isNotEmpty(); + assertThat(result.getErrors()).isNotEmpty(); // This should pass assertThat(result.getErrors()) .extracting(BidderError::getMessage) .containsExactly("Unable to fetch mediaType 2 in multi-format: 123"); @@ -411,7 +411,7 @@ private static Imp givenImp(UnaryOperator impCustomizer) { .build().toBuilder()).build(); } - private static Imp givenImpWithNullSideId(UnaryOperator impCustomizer) { + private static Imp givenImpWithoutSiteId(UnaryOperator impCustomizer) { return impCustomizer.apply(Imp.builder() .id("123") .ext(mapper.valueToTree(ExtPrebid.of(null, From f3573d48a9a179ab477359e2565357d9e1df3ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Tue, 11 Jun 2024 12:23:00 +0200 Subject: [PATCH 16/18] resolve comments --- .../server/bidder/readpeak/ReadPeakBidder.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index 7a244b628a4..b95ac98cf35 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -106,25 +106,24 @@ private HttpRequest makeHttpRequest(BidRequest request, ExtImpReadPe final boolean hasApp = !hasSite && request.getApp() != null; final BidRequest outgoingRequest = requestBuilder - .site(hasSite ? modifySite(request.getSite(), extImpReadPeak, publisher) : request.getSite()) + .site(hasSite ? modifySite(request.getSite(), + extImpReadPeak.getSiteId(), publisher) : request.getSite()) .app(hasApp ? modifyApp(request.getApp(), extImpReadPeak, publisher) : request.getApp()) .build(); return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); } - private Site modifySite(Site site, ExtImpReadPeak extImpReadPeak, Publisher publisher) { + private Site modifySite(Site site, String siteId, Publisher publisher) { return site.toBuilder() - .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) - ? extImpReadPeak.getSiteId() : site.getId()) + .id(StringUtils.isNotBlank(siteId) ? siteId : site.getId()) .publisher(publisher) .build(); } private App modifyApp(App app, ExtImpReadPeak extImpReadPeak, Publisher publisher) { return app.toBuilder() - .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) - ? extImpReadPeak.getSiteId() : app.getId()) + .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) ? extImpReadPeak.getSiteId() : app.getId()) .publisher(publisher) .build(); } @@ -134,7 +133,7 @@ public Result> makeBids(BidderCall httpCall, BidRequ try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); return extractBids(bidResponse); - } catch (DecodeException | PreBidException e) { + } catch (DecodeException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } From 46755088e01d8d132a66ecde8d414e9c5ce7de13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Kaczmarek?= Date: Fri, 14 Jun 2024 12:04:42 +0200 Subject: [PATCH 17/18] resolve comments --- .../bidder/readpeak/ReadPeakBidder.java | 61 +++++----- .../bidder/readpeak/ReadPeakBidderTest.java | 108 +++++++++++++++--- 2 files changed, 118 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index b95ac98cf35..a92d1f34696 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -32,6 +32,7 @@ import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -130,55 +131,47 @@ private App modifyApp(App app, ExtImpReadPeak extImpReadPeak, Publisher publishe @Override public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + final List errors = new ArrayList<>(); try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return extractBids(bidResponse); + return Result.of(extractBids(bidResponse, errors), errors); } catch (DecodeException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private Result> extractBids(BidResponse bidResponse) { + private List extractBids(BidResponse bidResponse, List errors) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { - return Result.withValues(Collections.emptyList()); + return Collections.emptyList(); } - return bidsFromResponse(bidResponse); + return bidsFromResponse(bidResponse, errors); } - private Result> bidsFromResponse(BidResponse bidResponse) { - final List errors = new ArrayList<>(); - final List validBids = new ArrayList<>(); - - for (SeatBid seatBid : bidResponse.getSeatbid()) { - if (seatBid != null) { - for (Bid bid : seatBid.getBid()) { - if (bid != null) { - try { - final BidderBid bidderBid = makeBid(bid, bidResponse); - validBids.add(bidderBid); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); - } - } - } - } - } + private List bidsFromResponse(BidResponse bidResponse, List errors) { + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .map(bid -> makeBid(bid, bidResponse.getCur(), errors)) + .filter(Objects::nonNull) + .toList(); + } - if (!errors.isEmpty()) { - return Result.withErrors(errors); + private BidderBid makeBid(Bid bid, String currency, List errors) { + try { + final Bid resolvedBid = resolveMacros(bid); + final BidType bidType = getBidType(bid); + final Bid updatedBid = addBidMeta(resolvedBid); + return BidderBid.of(updatedBid, bidType, currency); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + return null; } - return Result.withValues(validBids); - } - - private BidderBid makeBid(Bid bid, BidResponse bidResponse) { - final Bid resolvedBid = resolveMacros(bid); - final BidType bidType = getBidType(bid); - final Bid updatedBid = addBidMeta(resolvedBid); - return BidderBid.of(updatedBid, bidType, bidResponse.getCur()); } - private Bid resolveMacros(Bid bid) { + private static Bid resolveMacros(Bid bid) { final BigDecimal price = bid.getPrice(); final String priceAsString = price != null ? price.toPlainString() : "0"; @@ -189,7 +182,7 @@ private Bid resolveMacros(Bid bid) { .build(); } - private BidType getBidType(Bid bid) { + private static BidType getBidType(Bid bid) { final Integer markupType = ObjectUtils.defaultIfNull(bid.getMtype(), 0); return switch (markupType) { diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index 787635a4dc4..e9b3b2b1306 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -35,6 +35,10 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; +import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER; +import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE; +import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER; +import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; public class ReadPeakBidderTest extends VertxTest { @@ -83,8 +87,8 @@ public void makeHttpRequestsShouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).hasSize(1); // Oczekujemy jednego błędu dla niepoprawnej impresji - assertThat(result.getValue()).hasSize(1); // Oczekujemy jednego żądania HTTP + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getValue()).hasSize(1); final byte[] requestBody = result.getValue().get(0).getBody(); final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); @@ -144,6 +148,78 @@ public void makeHttpRequestsShouldUseBidFloorFromImpIfValid() throws IOException assertThat(capturedImp.getBidfloor()).isEqualByComparingTo(validBidFloor); } + @Test + public void makeHttpRequestsShouldUseCorrectUri() { + // given + final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getUri) + .containsExactly(ENDPOINT_URL); + } + + @Test + public void makeHttpRequestsShouldReturnExpectedHeaders() { + // given + final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getHeaders) + .satisfies(headers -> assertThat(headers.get(CONTENT_TYPE_HEADER)) + .isEqualTo(APPLICATION_JSON_CONTENT_TYPE)) + .satisfies(headers -> assertThat(headers.get(ACCEPT_HEADER)) + .isEqualTo(APPLICATION_JSON_VALUE)); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldIncludeCorrectPayload() throws IOException { + // given + final Imp imp = givenImp(impBuilder -> impBuilder.id("123")); + final BidRequest bidRequest = BidRequest.builder() + .imp(Collections.singletonList(imp)) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + + final byte[] requestBody = result.getValue().get(0).getBody(); + final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); + + assertThat(capturedBidRequest.getImp()).hasSize(1); + assertThat(capturedBidRequest.getImp().get(0).getId()).isEqualTo("123"); + } + + @Test + public void makeHttpRequestsShouldIncludeImpIds() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.id("imp1")); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getId) + .containsExactly("imp1"); + } + @Test public void makeHttpRequestsShouldSetSitePublisher() throws IOException { // given @@ -218,10 +294,9 @@ public void makeHttpRequestsShouldUseSiteIdForAppWhenSiteIdIsNotBlank() throws I } @Test - public void makeBidsShouldReturnEmptyListIfBidResponseIsNull2() throws JsonProcessingException { + public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(null)); + final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null)); // when final Result> result = target.makeBids(httpCall, null); @@ -271,8 +346,7 @@ public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { BidRequest.builder() .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(1)))); + givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(1))); // when final Result> result = target.makeBids(httpCall, null); @@ -292,8 +366,7 @@ public void makeBidsShouldReturnNativeBid() throws JsonProcessingException { BidRequest.builder() .imp(singletonList(Imp.builder().id("123").xNative(Native.builder().build()).build())) .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(4)))); + givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(4))); // when final Result> result = target.makeBids(httpCall, null); @@ -313,14 +386,13 @@ public void makeBidsShouldReturnErrorForUnsupportedMType() throws JsonProcessing BidRequest.builder() .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(2)))); + givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(2))); // when final Result> result = target.makeBids(httpCall, null); // then - assertThat(result.getErrors()).isNotEmpty(); // This should pass + assertThat(result.getErrors()).isNotEmpty(); assertThat(result.getErrors()) .extracting(BidderError::getMessage) .containsExactly("Unable to fetch mediaType 2 in multi-format: 123"); @@ -336,8 +408,7 @@ public void makeBidsShouldReturnErrorForMissingMType() throws JsonProcessingExce BidRequest.builder() .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123")))); + givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123"))); // when final Result> result = target.makeBids(httpCall, null); @@ -426,12 +497,15 @@ private static Imp givenBadImp(UnaryOperator impCustomizer) { .build().toBuilder()).build(); } - private static BidResponse givenBidResponse(UnaryOperator bidCustomizer) { - return BidResponse.builder() + private static String givenBidResponse(UnaryOperator bidCustomizer) throws JsonProcessingException { + final BidResponse bidResponse = BidResponse.builder() .cur("USD") - .seatbid(List.of(SeatBid.builder().bid(List.of(bidCustomizer.apply(Bid.builder().id("123")).build())) + .seatbid(List.of(SeatBid.builder() + .bid(List.of(bidCustomizer.apply(Bid.builder().id("123")).build())) .build())) .build(); + + return mapper.writeValueAsString(bidResponse); } private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { From 704c4d60451ee609eeb4d58becb5576358fea016 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Tue, 2 Jul 2024 10:28:59 +0200 Subject: [PATCH 18/18] Fix comments --- .../bidder/readpeak/ReadPeakBidder.java | 85 ++--- .../bidder/readpeak/ReadPeakBidderTest.java | 290 +++++++++--------- 2 files changed, 186 insertions(+), 189 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java index a92d1f34696..a24cbf044c6 100644 --- a/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java +++ b/src/main/java/org/prebid/server/bidder/readpeak/ReadPeakBidder.java @@ -1,7 +1,6 @@ package org.prebid.server.bidder.readpeak; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; @@ -36,6 +35,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; public class ReadPeakBidder implements Bidder { @@ -43,6 +43,10 @@ public class ReadPeakBidder implements Bidder { new TypeReference<>() { }; + private static final TypeReference> EXT_PREBID_TYPE_REFERENCE = + new TypeReference<>() { + }; + private static final String PRICE_MACRO = "${AUCTION_PRICE}"; private final String endpointUrl; @@ -56,27 +60,26 @@ public ReadPeakBidder(String endpointUrl, JacksonMapper mapper) { @Override public Result>> makeHttpRequests(BidRequest request) { final List modifiedImps = new ArrayList<>(); - final List extImpReadPeaks = new ArrayList<>(); final List errors = new ArrayList<>(); + ExtImpReadPeak extImp = null; for (Imp imp : request.getImp()) { try { - final ExtImpReadPeak extImpReadPeak = parseImpExt(imp); - final Imp modifiedImp = modifyImp(imp, extImpReadPeak); + extImp = parseImpExt(imp); + final Imp modifiedImp = modifyImp(imp, extImp); modifiedImps.add(modifiedImp); - extImpReadPeaks.add(extImpReadPeak); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); } } if (modifiedImps.isEmpty()) { - return Result.withError(BidderError - .badInput(String.format("Failed to find compatible impressions for request %s", request.getId()))); + return Result.withError(BidderError.badInput( + String.format("Failed to find compatible impressions for request %s", request.getId()))); } final BidRequest modifiedRequest = request.toBuilder().imp(modifiedImps).build(); - final HttpRequest httpRequest = makeHttpRequest(modifiedRequest, extImpReadPeaks.get(0)); + final HttpRequest httpRequest = makeHttpRequest(modifiedRequest, extImp); return Result.of(Collections.singletonList(httpRequest), errors); } @@ -96,39 +99,34 @@ private Imp modifyImp(Imp imp, ExtImpReadPeak extImpReadPeak) { .build(); } - private HttpRequest makeHttpRequest(BidRequest request, ExtImpReadPeak extImpReadPeak) { - final Publisher publisher = Publisher.builder() - .id(extImpReadPeak.getPublisherId()) - .build(); - - final BidRequest.BidRequestBuilder requestBuilder = request.toBuilder(); - - final boolean hasSite = request.getSite() != null; - final boolean hasApp = !hasSite && request.getApp() != null; - - final BidRequest outgoingRequest = requestBuilder - .site(hasSite ? modifySite(request.getSite(), - extImpReadPeak.getSiteId(), publisher) : request.getSite()) - .app(hasApp ? modifyApp(request.getApp(), extImpReadPeak, publisher) : request.getApp()) - .build(); - - return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); - } - - private Site modifySite(Site site, String siteId, Publisher publisher) { + private static Site modifySite(Site site, String siteId, Publisher publisher) { return site.toBuilder() .id(StringUtils.isNotBlank(siteId) ? siteId : site.getId()) .publisher(publisher) .build(); } - private App modifyApp(App app, ExtImpReadPeak extImpReadPeak, Publisher publisher) { + private static App modifyApp(App app, ExtImpReadPeak extImp, Publisher publisher) { return app.toBuilder() - .id(StringUtils.isNotBlank(extImpReadPeak.getSiteId()) ? extImpReadPeak.getSiteId() : app.getId()) + .id(StringUtils.isNotBlank(extImp.getSiteId()) ? extImp.getSiteId() : app.getId()) .publisher(publisher) .build(); } + private HttpRequest makeHttpRequest(BidRequest request, ExtImpReadPeak extImp) { + final Publisher publisher = Publisher.builder().id(extImp.getPublisherId()).build(); + + final boolean hasSite = request.getSite() != null; + final boolean hasApp = !hasSite && request.getApp() != null; + + final BidRequest outgoingRequest = request.toBuilder() + .site(hasSite ? modifySite(request.getSite(), extImp.getSiteId(), publisher) : null) + .app(hasApp ? modifyApp(request.getApp(), extImp, publisher) : null) + .build(); + + return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); + } + @Override public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { final List errors = new ArrayList<>(); @@ -194,23 +192,32 @@ private static BidType getBidType(Bid bid) { } private Bid addBidMeta(Bid bid) { - final ExtBidPrebidMeta extBidPrebidMeta = ExtBidPrebidMeta.builder() - .advertiserDomains(bid.getAdomain()) - .build(); + final ExtBidPrebid prebid = parseExtBidPrebid(bid); - final ExtBidPrebidMeta modifiedMeta = ExtBidPrebidMeta.builder() + final ExtBidPrebidMeta modifiedMeta = Optional.ofNullable(prebid).map(ExtBidPrebid::getMeta) + .map(ExtBidPrebidMeta::toBuilder) + .orElseGet(ExtBidPrebidMeta::builder) .advertiserDomains(bid.getAdomain()) .build(); - final ExtBidPrebid modifiedPrebid = ExtBidPrebid.builder() + final ExtBidPrebid modifiedPrebid = Optional.ofNullable(prebid) + .map(ExtBidPrebid::toBuilder) + .orElseGet(ExtBidPrebid::builder) .meta(modifiedMeta) .build(); - final ObjectNode modifiedBidExt = mapper.mapper() - .valueToTree(ExtPrebid.of(modifiedPrebid, null)); - return bid.toBuilder() - .ext(modifiedBidExt) + .ext(mapper.mapper().valueToTree(ExtPrebid.of(modifiedPrebid, null))) .build(); } + + private ExtBidPrebid parseExtBidPrebid(Bid bid) { + try { + return Optional.ofNullable(mapper.mapper().convertValue(bid.getExt(), EXT_PREBID_TYPE_REFERENCE)) + .map(ExtPrebid::getPrebid) + .orElse(null); + } catch (IllegalArgumentException e) { + return null; + } + } } diff --git a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java index e9b3b2b1306..760669e733d 100644 --- a/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/readpeak/ReadPeakBidderTest.java @@ -1,12 +1,10 @@ package org.prebid.server.bidder.readpeak; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; -import com.iab.openrtb.request.Native; import com.iab.openrtb.request.Site; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; @@ -22,8 +20,8 @@ import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.readpeak.ExtImpReadPeak; import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta; -import java.io.IOException; import java.math.BigDecimal; import java.util.Collections; import java.util.List; @@ -31,8 +29,10 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; +import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.tuple; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER; @@ -52,7 +52,7 @@ public void creationShouldFailOnInvalidEndpointUrl() { } @Test - public void makeHttpRequestsShouldContainAllImpsInOneRequest() throws IOException { + public void makeHttpRequestsShouldContainAllImpsInOneRequest() { // given final Imp imp1 = givenImp(imp -> imp.id("imp1")); final Imp imp2 = givenImp(imp -> imp.id("imp2")); @@ -65,17 +65,38 @@ public void makeHttpRequestsShouldContainAllImpsInOneRequest() throws IOExceptio // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp).hasSize(2) + .extracting(Imp::getId).containsExactly("imp1", "imp2"); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getBody) + .extracting(body -> mapper.readValue(body, BidRequest.class)) + .flatExtracting(BidRequest::getImp).hasSize(2) + .extracting(Imp::getId).containsExactly("imp1", "imp2"); + } - final byte[] requestBody = result.getValue().get(0).getBody(); - final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); + @Test + public void makeHttpRequestsShouldIncludeImpIds() { + // given + final Imp imp1 = givenImp(imp -> imp.id("imp1")); + final Imp imp2 = givenImp(imp -> imp.id("imp2")); + final BidRequest bidRequest = BidRequest.builder() + .imp(asList(imp1, imp2)) + .build(); - assertThat(capturedBidRequest.getImp()).hasSize(2); - assertThat(capturedBidRequest.getImp()).extracting(Imp::getId).containsExactly("imp1", "imp2"); + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .flatExtracting(HttpRequest::getImpIds) + .containsExactly("imp1", "imp2"); } @Test - public void makeHttpRequestsShouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot() throws IOException { + public void makeHttpRequestsShouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot() { // given final Imp validImp = givenImp(imp -> imp.id("validImp")); final Imp invalidImp = givenBadImp(imp -> imp.id("invalidImp")); @@ -88,13 +109,15 @@ public void makeHttpRequestsShouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot // then assertThat(result.getErrors()).hasSize(1); - assertThat(result.getValue()).hasSize(1); - - final byte[] requestBody = result.getValue().get(0).getBody(); - final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); - - assertThat(capturedBidRequest.getImp()).hasSize(1); - assertThat(capturedBidRequest.getImp()).extracting(Imp::getId).containsExactly("validImp"); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp).hasSize(1) + .extracting(Imp::getId).containsExactly("validImp"); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getBody) + .extracting(body -> mapper.readValue(body, BidRequest.class)) + .flatExtracting(BidRequest::getImp).hasSize(1) + .extracting(Imp::getId).containsExactly("validImp"); } @Test @@ -116,7 +139,7 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCannotBeParsed() { } @Test - public void makeHttpRequestsShouldUseBidFloorFromImpIfValid() throws IOException { + public void makeHttpRequestsShouldUseBidFloorFromImpIfValid() { // given final BigDecimal validBidFloor = new BigDecimal("1.23"); final String bidFloorCurrency = "USD"; @@ -139,19 +162,17 @@ public void makeHttpRequestsShouldUseBidFloorFromImpIfValid() throws IOException // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); - - final byte[] requestBody = result.getValue().get(0).getBody(); - final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); - final Imp capturedImp = capturedBidRequest.getImp().get(0); - - assertThat(capturedImp.getBidfloor()).isEqualByComparingTo(validBidFloor); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBidfloor) + .containsExactly(validBidFloor); } @Test public void makeHttpRequestsShouldUseCorrectUri() { // given - final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + final BidRequest bidRequest = givenBidRequest(identity()); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -166,7 +187,7 @@ public void makeHttpRequestsShouldUseCorrectUri() { @Test public void makeHttpRequestsShouldReturnExpectedHeaders() { // given - final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + final BidRequest bidRequest = givenBidRequest(identity()); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -182,51 +203,11 @@ public void makeHttpRequestsShouldReturnExpectedHeaders() { } @Test - public void makeHttpRequestsShouldIncludeCorrectPayload() throws IOException { - // given - final Imp imp = givenImp(impBuilder -> impBuilder.id("123")); - final BidRequest bidRequest = BidRequest.builder() - .imp(Collections.singletonList(imp)) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); - - final byte[] requestBody = result.getValue().get(0).getBody(); - final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); - - assertThat(capturedBidRequest.getImp()).hasSize(1); - assertThat(capturedBidRequest.getImp().get(0).getId()).isEqualTo("123"); - } - - @Test - public void makeHttpRequestsShouldIncludeImpIds() { + public void makeHttpRequestsShouldSetSitePublisher() { // given - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.id("imp1")); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getId) - .containsExactly("imp1"); - } - - @Test - public void makeHttpRequestsShouldSetSitePublisher() throws IOException { - // given - final Site site = Site.builder().id("siteId").build(); final Imp validImp = givenImp(imp -> imp.id("validImp")); final BidRequest bidRequest = BidRequest.builder() - .site(site) + .site(Site.builder().id("siteId").build()) .imp(Collections.singletonList(validImp)) .build(); @@ -235,23 +216,19 @@ public void makeHttpRequestsShouldSetSitePublisher() throws IOException { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); - - final byte[] requestBody = result.getValue().get(0).getBody(); - final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); - assertThat(capturedBidRequest.getSite()).isNotNull(); - assertThat(capturedBidRequest.getSite().getPublisher()).isNotNull(); - assertThat(capturedBidRequest.getSite().getPublisher().getId()).isEqualTo("publisherId"); - assertThat(capturedBidRequest.getSite().getId()).isEqualTo("siteId"); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getSite) + .extracting(Site::getId, site -> site.getPublisher().getId()) + .containsExactly(tuple("siteId", "publisherId")); } @Test - public void makeHttpRequestsShouldSetAppPublisher() throws IOException { + public void makeHttpRequestsShouldSetAppPublisher() { // given - final App app = App.builder().id("appId").build(); final Imp validImp = givenImpWithoutSiteId(imp -> imp.id("validImp")); final BidRequest bidRequest = BidRequest.builder() - .app(app) + .app(App.builder().id("appId").build()) .imp(Collections.singletonList(validImp)) .build(); // when @@ -259,23 +236,19 @@ public void makeHttpRequestsShouldSetAppPublisher() throws IOException { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); - - final byte[] requestBody = result.getValue().get(0).getBody(); - final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); - assertThat(capturedBidRequest.getApp()).isNotNull(); - assertThat(capturedBidRequest.getApp().getPublisher()).isNotNull(); - assertThat(capturedBidRequest.getApp().getPublisher().getId()).isEqualTo("publisherId"); - assertThat(capturedBidRequest.getApp().getId()).isEqualTo("appId"); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getApp) + .extracting(App::getId, app -> app.getPublisher().getId()) + .containsExactly(tuple("appId", "publisherId")); } @Test - public void makeHttpRequestsShouldUseSiteIdForAppWhenSiteIdIsNotBlank() throws IOException { + public void makeHttpRequestsShouldUseSiteIdForAppWhenSiteIdIsNotBlank() { // given - final App app = App.builder().id("appId").build(); final Imp validImp = givenImp(imp -> imp.id("validImp")); final BidRequest bidRequest = BidRequest.builder() - .app(app) + .app(App.builder().id("appId").build()) .imp(Collections.singletonList(validImp)) .build(); // when @@ -283,20 +256,17 @@ public void makeHttpRequestsShouldUseSiteIdForAppWhenSiteIdIsNotBlank() throws I // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); - - final byte[] requestBody = result.getValue().get(0).getBody(); - final BidRequest capturedBidRequest = mapper.readValue(requestBody, BidRequest.class); - assertThat(capturedBidRequest.getApp()).isNotNull(); - assertThat(capturedBidRequest.getApp().getPublisher()).isNotNull(); - assertThat(capturedBidRequest.getApp().getPublisher().getId()).isEqualTo("publisherId"); - assertThat(capturedBidRequest.getApp().getId()).isEqualTo("siteId"); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getApp) + .extracting(App::getId, app -> app.getPublisher().getId()) + .containsExactly(tuple("siteId", "publisherId")); } @Test public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null)); + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(null)); // when final Result> result = target.makeBids(httpCall, null); @@ -309,7 +279,7 @@ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProces @Test public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed2() { // given - final BidderCall httpCall = givenHttpCall(null, "invalid"); + final BidderCall httpCall = givenHttpCall("invalid"); // when final Result> result = target.makeBids(httpCall, null); @@ -326,8 +296,7 @@ public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed2() { @Test public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull2() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(BidResponse.builder().build())); + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(BidResponse.builder().build())); // when final Result> result = target.makeBids(httpCall, null); @@ -340,13 +309,8 @@ public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull2() throws Js @Test public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { // given - final ObjectNode bidExt = mapper.createObjectNode() - .putPOJO("prebid", ExtBidPrebid.builder().type(banner).build()); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) - .build(), - givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(1))); + final BidderCall httpCall = givenHttpCall(givenBidResponse( + bidBuilder -> bidBuilder.impid("123").mtype(1))); // when final Result> result = target.makeBids(httpCall, null); @@ -360,13 +324,8 @@ public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { @Test public void makeBidsShouldReturnNativeBid() throws JsonProcessingException { // given - final ObjectNode bidExt = mapper.createObjectNode() - .putPOJO("prebid", ExtBidPrebid.builder().type(xNative).build()); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").xNative(Native.builder().build()).build())) - .build(), - givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(4))); + final BidderCall httpCall = givenHttpCall(givenBidResponse( + bidBuilder -> bidBuilder.impid("123").mtype(4))); // when final Result> result = target.makeBids(httpCall, null); @@ -380,13 +339,8 @@ public void makeBidsShouldReturnNativeBid() throws JsonProcessingException { @Test public void makeBidsShouldReturnErrorForUnsupportedMType() throws JsonProcessingException { // given - final ObjectNode bidExt = mapper.createObjectNode() - .putPOJO("prebid", ExtBidPrebid.builder().type(banner).build()); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) - .build(), - givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123").mtype(2))); + final BidderCall httpCall = givenHttpCall(givenBidResponse( + bidBuilder -> bidBuilder.impid("123").mtype(2))); // when final Result> result = target.makeBids(httpCall, null); @@ -402,13 +356,8 @@ public void makeBidsShouldReturnErrorForUnsupportedMType() throws JsonProcessing @Test public void makeBidsShouldReturnErrorForMissingMType() throws JsonProcessingException { // given - final ObjectNode bidExt = mapper.createObjectNode() - .putPOJO("prebid", ExtBidPrebid.builder().type(banner).build()); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) - .build(), - givenBidResponse(bidBuilder -> bidBuilder.ext(bidExt).impid("123"))); + final BidderCall httpCall = givenHttpCall(givenBidResponse( + bidBuilder -> bidBuilder.mtype(null).impid("123"))); // when final Result> result = target.makeBids(httpCall, null); @@ -422,7 +371,7 @@ public void makeBidsShouldReturnErrorForMissingMType() throws JsonProcessingExce } @Test - public void makeBidsShouldCreateBidExtIfMissing() throws JsonProcessingException { + public void makeBidsShouldCreateExtBidPrebidMetaWithADomains() throws JsonProcessingException { // given final Bid bid = Bid.builder() .id("1") @@ -431,34 +380,75 @@ public void makeBidsShouldCreateBidExtIfMissing() throws JsonProcessingException .nurl("${AUCTION_PRICE}") .impid("123") .mtype(1) + .adomain(List.of("domain1", "domain2")) .build(); final BidResponse bidResponse = BidResponse.builder() .cur("USD") - .seatbid(List.of(SeatBid.builder() - .bid(List.of(bid)) - .build())) + .seatbid(List.of(SeatBid.builder().bid(List.of(bid)).build())) + .build(); + + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(bidResponse)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + + final ExtBidPrebidMeta expectedMeta = ExtBidPrebidMeta.builder() + .advertiserDomains(List.of("domain1", "domain2")) + .build(); + assertThat(result.getValue()).hasSize(1) + .extracting(BidderBid::getBid) + .extracting(Bid::getExt) + .extracting(ext -> ext.get("prebid")) + .extracting(prebid -> prebid.get("meta")) + .containsExactly(mapper.valueToTree(expectedMeta)); + } + + @Test + public void makeBidsShouldModifyExistingExtBidPrebidMetaWithADomains() throws JsonProcessingException { + // given + final Bid bid = Bid.builder() + .id("1") + .price(BigDecimal.valueOf(1.23)) + .adm("${AUCTION_PRICE}") + .nurl("${AUCTION_PRICE}") + .impid("123") + .mtype(1) + .adomain(List.of("domain1", "domain2")) + .ext(mapper.createObjectNode().set( + "prebid", + mapper.valueToTree(ExtBidPrebid.builder() + .meta(ExtBidPrebidMeta.builder().networkId(1).build()) + .build()))) .build(); - final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build())) - .build(), - mapper.writeValueAsString(bidResponse)); + final BidResponse bidResponse = BidResponse.builder() + .cur("USD") + .seatbid(List.of(SeatBid.builder().bid(List.of(bid)).build())) + .build(); + + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(bidResponse)); // when final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); - final BidderBid bidderBid = result.getValue().get(0); - final Bid updatedBid = bidderBid.getBid(); + final ExtBidPrebidMeta expectedMeta = ExtBidPrebidMeta.builder() + .networkId(1) + .advertiserDomains(List.of("domain1", "domain2")) + .build(); - assertThat(updatedBid.getExt()).isNotNull(); - assertThat(updatedBid.getExt().has("prebid")).isTrue(); - assertThat(updatedBid.getExt().get("prebid").has("meta")).isTrue(); + assertThat(result.getValue()).hasSize(1) + .extracting(BidderBid::getBid) + .extracting(Bid::getExt) + .extracting(ext -> ext.get("prebid")) + .extracting(prebid -> prebid.get("meta")) + .containsExactly(mapper.valueToTree(expectedMeta)); } private static BidRequest givenBidRequest( @@ -471,7 +461,7 @@ private static BidRequest givenBidRequest( } private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { - return givenBidRequest(UnaryOperator.identity(), impCustomizer); + return givenBidRequest(identity(), impCustomizer); } private static Imp givenImp(UnaryOperator impCustomizer) { @@ -493,7 +483,7 @@ private static Imp givenImpWithoutSiteId(UnaryOperator impCustom private static Imp givenBadImp(UnaryOperator impCustomizer) { return impCustomizer.apply(Imp.builder() .id("invalidImp") - .ext(mapper.createObjectNode().put("bidder", "invalidValue")) // nieprawidłowa wartość powodująca błąd parsowania + .ext(mapper.createObjectNode().put("bidder", "invalidValue")) .build().toBuilder()).build(); } @@ -508,9 +498,9 @@ private static String givenBidResponse(UnaryOperator bidCustomiz return mapper.writeValueAsString(bidResponse); } - private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { + private static BidderCall givenHttpCall(String body) { return BidderCall.succeededHttp( - HttpRequest.builder().payload(bidRequest).build(), + HttpRequest.builder().build(), HttpResponse.of(200, null, body), null); }