From 72c3f88228d5e58aca25033bc299917138fadd1f Mon Sep 17 00:00:00 2001 From: markiian Date: Tue, 23 Jan 2024 09:18:46 +0200 Subject: [PATCH 1/7] Add new AdQuery bidder --- .../server/bidder/adquery/AdQueryBidder.java | 213 ++++++++ .../adquery/model/request/AdQueryRequest.java | 57 +++ .../model/response/AdQueryDataResponse.java | 37 ++ .../model/response/AdQueryMediaType.java | 14 + .../model/response/AdQueryResponse.java | 10 + .../ext/request/adquery/ExtImpAdQuery.java | 13 + .../config/bidder/AdQueryConfiguration.java | 41 ++ src/main/resources/bidder-config/adquery.yaml | 16 + .../static/bidder-params/adquery.json | 23 + .../bidder/adquery/AdQueryBidderTest.java | 480 ++++++++++++++++++ .../org/prebid/server/it/AdQueryTest.java | 38 ++ .../adquery/test-adquery-bid-request.json | 16 + .../adquery/test-adquery-bid-response.json | 23 + .../adquery/test-auction-adquery-request.json | 24 + .../test-auction-adquery-response.json | 40 ++ .../server/it/test-application.properties | 2 + 16 files changed, 1047 insertions(+) create mode 100644 src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java create mode 100644 src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java create mode 100644 src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java create mode 100644 src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryMediaType.java create mode 100644 src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryResponse.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/adquery/ExtImpAdQuery.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/AdQueryConfiguration.java create mode 100644 src/main/resources/bidder-config/adquery.yaml create mode 100644 src/main/resources/static/bidder-params/adquery.json create mode 100644 src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java create mode 100644 src/test/java/org/prebid/server/it/AdQueryTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adquery/test-adquery-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adquery/test-adquery-bid-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-response.json diff --git a/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java new file mode 100644 index 00000000000..8bad310c80b --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java @@ -0,0 +1,213 @@ +package org.prebid.server.bidder.adquery; + +import com.fasterxml.jackson.core.io.BigDecimalParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; +import com.iab.openrtb.request.Format; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.User; +import com.iab.openrtb.response.Bid; +import io.vertx.core.MultiMap; +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.adquery.model.request.AdQueryRequest; +import org.prebid.server.bidder.adquery.model.response.AdQueryDataResponse; +import org.prebid.server.bidder.adquery.model.response.AdQueryMediaType; +import org.prebid.server.bidder.adquery.model.response.AdQueryResponse; +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.adquery.ExtImpAdQuery; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; +import org.prebid.server.util.HttpUtil; +import org.prebid.server.util.ObjectUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class AdQueryBidder implements Bidder { + + private static final TypeReference> AD_QUERY_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; + private static final String PREBID_VERSION = "server"; + private static final String BIDDER_NAME = "adquery"; + private static final String DEFAULT_CURRENCY = "PLN"; + private static final String ORTB_VERSION = "2.5"; + private static final String ADM_TEMPLATE = "%s"; + + private final String endpointUrl; + private final JacksonMapper mapper; + + public AdQueryBidder(String endpointUrl, JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final List errors = new ArrayList<>(); + final List> httpRequests = new ArrayList<>(); + + for (Imp imp : request.getImp()) { + final ExtImpAdQuery extImpAdQuery; + try { + extImpAdQuery = parseImpExt(imp); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + continue; + } + httpRequests.add(createRequest(request, imp, extImpAdQuery)); + } + + return Result.of(httpRequests, errors); + } + + private ExtImpAdQuery parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), AD_QUERY_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage()); + } + } + + private HttpRequest createRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { + final AdQueryRequest outgoingRequest = createAdQueryRequest(bidRequest, imp, extImpAdQuery); + + return HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpointUrl) + .headers(resolveHeader(bidRequest.getDevice())) + .impIds(BidderUtil.impIds(bidRequest)) + .payload(outgoingRequest) + .body(mapper.encodeToBytes(outgoingRequest)) + .build(); + } + + private AdQueryRequest createAdQueryRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { + return AdQueryRequest.builder() + .v(PREBID_VERSION) + .placementCode(extImpAdQuery.getPlacementId()) + .auctionId(StringUtils.EMPTY) + .type(extImpAdQuery.getType()) + .adUnitCode(imp.getTagid()) + .bidQid(ObjectUtil.getIfNotNullOrDefault(bidRequest.getUser(), User::getId, () -> StringUtils.EMPTY)) + .bidId(String.format("%s%s", bidRequest.getId(), imp.getId())) + .bidder(BIDDER_NAME) + .bidderRequestId(bidRequest.getId()) + .bidRequestsCount(1) + .bidderRequestsCount(1) + .sizes(getImpSizes(imp)) + .bidIp(ObjectUtil.getIfNotNull(bidRequest.getDevice(), Device::getIp)) + .bidIpv6(ObjectUtil.getIfNotNull(bidRequest.getDevice(), Device::getIpv6)) + .bidUa(ObjectUtil.getIfNotNull(bidRequest.getDevice(), Device::getUa)) + .bidPageUrl(ObjectUtil.getIfNotNull(bidRequest.getSite(), Site::getPage)) + .build(); + + } + + private String getImpSizes(Imp imp) { + final Banner banner = imp.getBanner(); + if (banner == null) { + return StringUtils.EMPTY; + } + + final List format = banner.getFormat(); + if (CollectionUtils.isNotEmpty(format)) { + final List sizes = new ArrayList<>(); + format.forEach(singleFormat -> sizes.add( + "%sx%s".formatted(getIntOrElseZero(singleFormat.getW()), getIntOrElseZero(singleFormat.getH())))); + return String.join("_", sizes); + } + + final Integer w = banner.getW(); + final Integer h = banner.getH(); + if (w != null && h != null) { + return "%sx%s".formatted(w, h); + } + + return StringUtils.EMPTY; + } + + private int getIntOrElseZero(Integer number) { + return number != null ? number : 0; + } + + private MultiMap resolveHeader(Device device) { + final MultiMap headers = HttpUtil.headers(); + headers.add(HttpUtil.X_OPENRTB_VERSION_HEADER, ORTB_VERSION); + + if (Objects.nonNull(device) && StringUtils.isNotBlank(device.getIp()) && device.getIp().length() > 0) { + headers.add(HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp()); + } + + return headers; + } + + @Override + public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final AdQueryResponse bidResponse = mapper.decodeValue( + httpCall.getResponse().getBody(), AdQueryResponse.class); + return Result.withValues(extractBids(bidResponse, bidRequest)); + } catch (DecodeException | PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private static List extractBids(AdQueryResponse adQueryResponse, BidRequest bidRequest) { + if (adQueryResponse == null || Objects.isNull(adQueryResponse.getData())) { + return Collections.emptyList(); + } + + final AdQueryDataResponse data = adQueryResponse.getData(); + final Bid bid = Bid.builder() + .id(data.getRequestId()) + .impid(resoleImpId(bidRequest, data)) + .price(BigDecimalParser.parse(data.getCpm())) + .adm(String.format(ADM_TEMPLATE, data.getAdqLib(), data.getTag())) + .adomain(data.getAdDomains()) + .crid(String.format("%d", data.getCreationId())) + .w(parseMeasure(ObjectUtil.getIfNotNull(data.getAdQueryMediaType(), AdQueryMediaType::getWidth))) + .h(parseMeasure(ObjectUtil.getIfNotNull(data.getAdQueryMediaType(), AdQueryMediaType::getHeight))) + .build(); + + return Collections.singletonList(BidderBid.of(bid, resolveMediaType(data), + StringUtils.isNotBlank(data.getCurrency()) ? data.getCurrency() : DEFAULT_CURRENCY)); + } + + private static String resoleImpId(BidRequest bidRequest, AdQueryDataResponse data) { + return Objects.nonNull(data.getRequestId()) + ? bidRequest.getId().replaceAll(data.getRequestId(), StringUtils.EMPTY) + : bidRequest.getId(); + } + + private static Integer parseMeasure(String measure) { + try { + return Integer.valueOf(measure); + } catch (NumberFormatException e) { + throw new PreBidException("Value of measure: %s can not be parsed.".formatted(measure)); + } + } + + private static BidType resolveMediaType(AdQueryDataResponse data) { + if (data.getAdQueryMediaType().getName() != BidType.banner) { + throw new PreBidException(String.format("Unsupported MediaType: %s", data.getAdQueryMediaType().getName())); + } + return data.getAdQueryMediaType().getName(); + } +} diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java b/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java new file mode 100644 index 00000000000..06d9e4c5bb8 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java @@ -0,0 +1,57 @@ +package org.prebid.server.bidder.adquery.model.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Builder(toBuilder = true) +@EqualsAndHashCode +@Getter +public class AdQueryRequest { + + String v; + + @JsonProperty("placementCode") + String placementCode; + + @JsonProperty("auctionId") + String auctionId; + + String type; + + @JsonProperty("AdUnitCode") + String adUnitCode; + + @JsonProperty("bidQid") + String bidQid; + + @JsonProperty("bidId") + String bidId; + + @JsonProperty("bidIp") + String bidIp; + + @JsonProperty("bidIpv6") + String bidIpv6; + + @JsonProperty("bidUa") + String bidUa; + + @JsonProperty("bidder") + String bidder; + + @JsonProperty("bidPageUrl") + String bidPageUrl; + + @JsonProperty("bidderRequestId") + String bidderRequestId; + + @JsonProperty("bidRequestsCount") + Integer bidRequestsCount; + + @JsonProperty("bidderRequestsCount") + Integer bidderRequestsCount; + + String sizes; +} diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java new file mode 100644 index 00000000000..d51985daa7c --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java @@ -0,0 +1,37 @@ +package org.prebid.server.bidder.adquery.model.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Builder(toBuilder = true) +@Getter +public class AdQueryDataResponse { + + @JsonProperty("requestId") + String requestId; + + @JsonProperty("creationId") + Integer creationId; + + String currency; + + String cpm; + + String code; + + @JsonProperty("adqLib") + String adqLib; + + String tag; + + @JsonProperty("adDomains") + List adDomains; + + String deadlid; + + @JsonProperty("mediaType") + AdQueryMediaType adQueryMediaType; +} diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryMediaType.java b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryMediaType.java new file mode 100644 index 00000000000..76b4b2257ec --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryMediaType.java @@ -0,0 +1,14 @@ +package org.prebid.server.bidder.adquery.model.response; + +import lombok.Value; +import org.prebid.server.proto.openrtb.ext.response.BidType; + +@Value(staticConstructor = "of") +public class AdQueryMediaType { + + BidType name; + + String width; + + String height; +} diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryResponse.java b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryResponse.java new file mode 100644 index 00000000000..0f97f113d7d --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryResponse.java @@ -0,0 +1,10 @@ +package org.prebid.server.bidder.adquery.model.response; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class AdQueryResponse { + + AdQueryDataResponse data; +} + diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adquery/ExtImpAdQuery.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adquery/ExtImpAdQuery.java new file mode 100644 index 00000000000..9a5cf8550ba --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adquery/ExtImpAdQuery.java @@ -0,0 +1,13 @@ +package org.prebid.server.proto.openrtb.ext.request.adquery; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpAdQuery { + + @JsonProperty("placementId") + String placementId; + + String type; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/AdQueryConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/AdQueryConfiguration.java new file mode 100644 index 00000000000..8c68cc8422c --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/AdQueryConfiguration.java @@ -0,0 +1,41 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.adquery.AdQueryBidder; +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 javax.validation.constraints.NotBlank; + +@Configuration +@PropertySource(value = "classpath:/bidder-config/adquery.yaml", factory = YamlPropertySourceFactory.class) +public class AdQueryConfiguration { + + private static final String BIDDER_NAME = "adquery"; + + @Bean("adqueryConfigurationProperties") + @ConfigurationProperties("adapters.adquery") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps adqueryBidderDeps(BidderConfigurationProperties adqueryConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(adqueryConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new AdQueryBidder(config.getEndpoint(), mapper)) + .assemble(); + } +} diff --git a/src/main/resources/bidder-config/adquery.yaml b/src/main/resources/bidder-config/adquery.yaml new file mode 100644 index 00000000000..543a16b8066 --- /dev/null +++ b/src/main/resources/bidder-config/adquery.yaml @@ -0,0 +1,16 @@ +adapters: + adquery: + endpoint: https://bidder2.adquery.io/prebid/bid + meta-info: + maintainer-email: prebid@adquery.io + app-media-types: + site-media-types: + - banner + supported-vendors: + vendor-id: 902 + usersync: + cookie-family-name: adquery + iframe: + url: https://api.adquery.io/storage?gdpr={{gdpr}}&consent={{gdpr_consent}}&ccpa_consent={{us_privacy}}&redirect={{redirect_url}} + support-cors: false + uid-macro: '$UID' diff --git a/src/main/resources/static/bidder-params/adquery.json b/src/main/resources/static/bidder-params/adquery.json new file mode 100644 index 00000000000..862e2de379d --- /dev/null +++ b/src/main/resources/static/bidder-params/adquery.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adquery Adapter Params", + "description": "A schema which validates params accepted by the Adquery adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "minLength": 35, + "maxLength": 45, + "description": "Placement ID" + }, + "type": { + "type": "string", + "minLength": 1, + "description": "Bid type" + } + }, + "required": [ + "placementId", + "type" + ] +} diff --git a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java new file mode 100644 index 00000000000..cabef2b6258 --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java @@ -0,0 +1,480 @@ +package org.prebid.server.bidder.adquery; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; +import com.iab.openrtb.request.Format; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.User; +import com.iab.openrtb.response.Bid; +import io.netty.handler.codec.http.HttpHeaderValues; +import org.assertj.core.groups.Tuple; +import org.junit.Test; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.adquery.model.request.AdQueryRequest; +import org.prebid.server.bidder.adquery.model.response.AdQueryDataResponse; +import org.prebid.server.bidder.adquery.model.response.AdQueryMediaType; +import org.prebid.server.bidder.adquery.model.response.AdQueryResponse; +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.adquery.ExtImpAdQuery; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.HttpUtil; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.function.UnaryOperator; + +import static java.util.Collections.singletonList; +import static java.util.function.UnaryOperator.identity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.tuple; + +public class AdQueryBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://test.endpoint.com/"; + + private final AdQueryBidder target = new AdQueryBidder(ENDPOINT_URL, jacksonMapper); + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new AdQueryBidder("invalid_url", jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldCorrectPopulateAdQueryRequest() { + // given + final BidRequest bidRequest = givenBidRequest(identity(), givenImp(identity())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .containsExactly(AdQueryRequest.builder() + .v("server") + .placementCode("pl1") + .auctionId("") + .type("6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897") + .adUnitCode("test-banner-imp-id") + .bidQid("user-id") + .bidId("22e26bd9a702bc1") + .bidIp("31.21.32.42") + .bidIpv6("131.123.632.32") + .bidUa("ua-test-value") + .bidder("adquery") + .bidPageUrl("https://www.unknowsite.com") + .bidderRequestId("22e26bd9a702bc") + .bidderRequestsCount(1) + .bidRequestsCount(1) + .sizes("320x100_300x250") + .build()); + } + + @Test + public void makeHttpRequestsShouldReturnErrorsOfNotValidImps() { + // given + final BidRequest bidRequest = givenBidRequest(identity(), givenImp(impBuilder -> impBuilder + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()) + .startsWith("Cannot deserialize value of type "); + } + + @Test + public void makeHttpRequestsShouldCorrectPopulateHeader() { + // given + final BidRequest bidRequest = givenBidRequest(identity(), givenImp(identity())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + // then + assertThat(result.getValue()) + .hasSize(1) + .flatExtracting(res -> res.getHeaders().entries()) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsExactlyInAnyOrder( + tuple(HttpUtil.CONTENT_TYPE_HEADER.toString(), HttpUtil.APPLICATION_JSON_CONTENT_TYPE), + tuple(HttpUtil.ACCEPT_HEADER.toString(), HttpHeaderValues.APPLICATION_JSON.toString()), + tuple(HttpUtil.X_OPENRTB_VERSION_HEADER.toString(), "2.5"), + tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "31.21.32.42")); + } + + @Test + public void makeHttpRequestsShouldNotPopulateXForwardedHeaderWhenDeviceDoesNotContainIp() { + // given + final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> + bidRequestBuilder.device(Device.builder().ip(null).build()), givenImp(identity())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .hasSize(1) + .flatExtracting(res -> res.getHeaders().entries()) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsExactlyInAnyOrder( + tuple(HttpUtil.CONTENT_TYPE_HEADER.toString(), HttpUtil.APPLICATION_JSON_CONTENT_TYPE), + tuple(HttpUtil.ACCEPT_HEADER.toString(), HttpHeaderValues.APPLICATION_JSON.toString()), + tuple(HttpUtil.X_OPENRTB_VERSION_HEADER.toString(), "2.5")); + } + + @Test + public void makeHttpRequestsShouldNotPopulateSeveralFieldWhenDeviceIsNull() { + // given + final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder.device(null), + givenImp(identity())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(AdQueryRequest::getBidIp, AdQueryRequest::getBidIpv6, AdQueryRequest::getBidUa) + .containsExactly(Tuple.tuple(null, null, null)); + } + + @Test + public void makeHttpRequestsShouldNotPopulateBidPageUrlFieldWhenSitePageIsNull() { + // given + final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder.site(null), + givenImp(identity())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(AdQueryRequest::getBidPageUrl) + .containsNull(); + } + + @Test + public void makeHttpRequestsShouldPopulateSizesFieldWithEmptyStringWhenBannerFormatIsEmpty() { + // given + final BidRequest bidRequest = givenBidRequest(identity(), + givenImp(impBuilder -> impBuilder.banner(Banner.builder().format(null).build()))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(AdQueryRequest::getSizes) + .containsExactly(""); + } + + @Test + public void makeHttpRequestsShouldCorrectPopulateSizesFieldWhenBannerContainWidthAndHeight() { + // given + final BidRequest bidRequest = givenBidRequest(identity(), + givenImp(impBuilder -> impBuilder.banner(Banner.builder().h(100).w(210).format(null).build()))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(AdQueryRequest::getSizes) + .containsExactly("210x100"); + } + + @Test + public void makeHttpRequestsShouldPopulateSizesFieldWithEmptyStringWhenBannerNull() { + // given + final BidRequest bidRequest = givenBidRequest(identity(), + givenImp(impBuilder -> impBuilder.banner(null))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .extracting(AdQueryRequest::getSizes) + .containsExactly(""); + } + + @Test + public void makeBidsShouldReturnErrorIfBidResponseBodyCouldNotBeParsed() { + // given + final BidderCall httpCall = givenHttpCall(null, "invalid"); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token"); + }); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListIfAdQueryResponseIsNull() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(null)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListIfAdQueryRequestDataIsNull() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(AdQueryResponse.of(null))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnErrorWhenAdQueryResponseMediaTypeDoesNotContainBannerType() + throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() + .requestId("abs") + .cpm("123") + .adQueryMediaType(AdQueryMediaType.of(BidType.audio, "120", "320")) + .build()))); + + // when + final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(error.getMessage()).startsWith("Unsupported MediaType: audio"); + }); + } + + @Test + public void makeBidsShouldReturnCorrectResponseBody() + throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() + .requestId("abs") + .cpm("123") + .adqLib("AnyLib") + .tag("AnyTag") + .adDomains(singletonList("any")) + .creationId(312) + .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "120", "320")) + .build()))); + + // when + final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); + + // then + assertThat(result.getValue()) + .extracting(BidderBid::getBidCurrency) + .hasSize(1) + .containsExactly("PLN"); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .hasSize(1) + .containsExactly(BidType.banner); + assertThat(result.getValue()) + .hasSize(1) + .extracting(BidderBid::getBid) + .containsExactly(Bid.builder() + .id("abs") + .impid("1") + .price(BigDecimal.valueOf(123)) + .adm("AnyTag") + .adomain(singletonList("any")) + .crid("312") + .w(120) + .h(320) + .build()); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnThrowErrorWheResponseBodyAdQueryMediaTypeWidthIsInvalid() + throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() + .requestId("abs") + .cpm("123") + .adqLib("AnyLib") + .tag("AnyTag") + .adDomains(singletonList("any")) + .creationId(312) + .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "ab", "320")) + .build()))); + + // when + final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .containsExactly(BidderError.badServerResponse("Value of measure: ab can not be parsed.")); + } + + @Test + public void makeBidsShouldReturnThrowErrorWheResponseBodyAdQueryMediaTypeHeightIsInvalid() + throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() + .requestId("abs") + .cpm("123") + .adqLib("AnyLib") + .tag("AnyTag") + .adDomains(singletonList("any")) + .creationId(312) + .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "312", "as")) + .build()))); + + // when + final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .containsExactly(BidderError.badServerResponse("Value of measure: as can not be parsed.")); + } + + @Test + public void makeBidsShouldCorrectPopulateCurrencyWhenCurrencyInResponseSpecified() + throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() + .requestId("abs") + .cpm("321") + .adqLib("AnyLib") + .currency("UAH") + .tag("AnyTag") + .adDomains(singletonList("any")) + .creationId(312) + .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "312", "321")) + .build()))); + + // when + final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); + + // then + assertThat(result.getValue()) + .hasSize(1) + .extracting(BidderBid::getBidCurrency) + .containsExactly("UAH"); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeBidsShouldUseDefaultCurrencyWhenCurrencyInResponseDoesNotSpecified() + throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() + .requestId("abs") + .cpm("321") + .adqLib("AnyLib") + .currency(null) + .tag("AnyTag") + .adDomains(singletonList("any")) + .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "312", "321")) + .build()))); + + // when + final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); + + // then + assertThat(result.getValue()) + .hasSize(1) + .extracting(BidderBid::getBidCurrency) + .containsExactly("PLN"); + assertThat(result.getErrors()).isEmpty(); + } + + private static BidRequest givenBidRequest(UnaryOperator requestCustomizer, + Imp... imps) { + return requestCustomizer.apply(BidRequest.builder() + .id("22e26bd9a702bc") + .imp(List.of(imps)) + .user(User.builder().id("user-id").build()) + .device(Device.builder() + .ip("31.21.32.42") + .ipv6("131.123.632.32") + .ua("ua-test-value") + .build()) + .site(Site.builder().page("https://www.unknowsite.com") + .build())).build(); + } + + private static Imp givenImp(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("1") + .tagid("test-banner-imp-id") + .banner(Banner.builder() + .format(List.of( + Format.builder().w(320).h(100).build(), + Format.builder().w(300).h(250).build())) + .build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpAdQuery.of("pl1", "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897"))))) + .build(); + } + + private static BidderCall givenHttpCall(String body) { + return BidderCall.succeededHttp(null, HttpResponse.of(200, null, body), null); + } + + private static BidderCall givenHttpCall(AdQueryRequest adQueryRequest, String body) { + return BidderCall.succeededHttp( + HttpRequest.builder().payload(adQueryRequest).build(), + HttpResponse.of(200, null, body), + null); + } +} diff --git a/src/test/java/org/prebid/server/it/AdQueryTest.java b/src/test/java/org/prebid/server/it/AdQueryTest.java new file mode 100644 index 00000000000..9ac52749f7e --- /dev/null +++ b/src/test/java/org/prebid/server/it/AdQueryTest.java @@ -0,0 +1,38 @@ +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 AdQueryTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromAdquery() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/adquery-exchange")) + .withRequestBody(equalToJson( + jsonFrom("openrtb2/adquery/test-adquery-bid-request.json"))) + .willReturn(aResponse().withBody( + jsonFrom("openrtb2/adquery/test-adquery-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/adquery/test-auction-adquery-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/adquery/test-auction-adquery-response.json", response, + singletonList("adquery")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-adquery-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-adquery-bid-request.json new file mode 100644 index 00000000000..4dbcc8373b5 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-adquery-bid-request.json @@ -0,0 +1,16 @@ +{ + "v": "server", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "auctionId": "", + "type": "banner", + "bidQid": "", + "bidId": "22e26bd9a702bc22e26bd9a702bc", + "bidIp": "193.168.244.1", + "bidUa": "userAgent", + "bidder": "adquery", + "bidPageUrl": "http://www.example.com", + "bidderRequestId": "22e26bd9a702bc", + "bidRequestsCount": 1, + "bidderRequestsCount": 1, + "sizes": "300x250" +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-adquery-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-adquery-bid-response.json new file mode 100644 index 00000000000..eddfa0b53d0 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-adquery-bid-response.json @@ -0,0 +1,23 @@ +{ + "data": { + "requestId": "22e26bd9a702bc1", + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "PLN", + "adDomains": [ + "unknownDomains" + ], + "tag": "Tag_Example", + "adqLib": "AdqLib_Example", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50" + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-request.json b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-request.json new file mode 100644 index 00000000000..fb245fc8cc8 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-request.json @@ -0,0 +1,24 @@ +{ + "id": "22e26bd9a702bc", + "imp": [ + { + "id": "22e26bd9a702bc", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "adquery": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-response.json new file mode 100644 index 00000000000..0d05040052c --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/adquery/test-auction-adquery-response.json @@ -0,0 +1,40 @@ +{ + "id": "22e26bd9a702bc", + "seatbid": [ + { + "bid": [ + { + "id": "22e26bd9a702bc1", + "impid": "22e26bd9a702bc", + "price": 1.090, + "adm": "Tag_Example", + "adomain": [ + "unknownDomains" + ], + "crid": "7211", + "w": 320, + "h": 50, + "ext": { + "origbidcpm": 4.14, + "origbidcur": "PLN", + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adquery", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adquery": "{{ adquery.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} 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 89478f9ee3a..05a0073b884 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -41,6 +41,8 @@ adapters.adview.enabled=true adapters.adview.endpoint=http://localhost:8090/adview-exchange?accountId={{AccountId}} adapters.adprime.enabled=true adapters.adprime.endpoint=http://localhost:8090/adprime-exchange +adapters.adquery.enabled=true +adapters.adquery.endpoint=http://localhost:8090/adquery-exchange adapters.adtarget.enabled=true adapters.adtarget.endpoint=http://localhost:8090/adtarget-exchange adapters.adtelligent.enabled=true From c074a7d3a9f2d17d34c7b5ce16eb27fd777ef403 Mon Sep 17 00:00:00 2001 From: markiian Date: Wed, 31 Jan 2024 17:52:32 +0200 Subject: [PATCH 2/7] Update after review --- .../server/bidder/adquery/AdQueryBidder.java | 74 +++++++++---------- .../adquery/model/request/AdQueryRequest.java | 9 +-- .../model/response/AdQueryDataResponse.java | 8 +- .../model/response/AdQueryMediaType.java | 4 +- .../bidder/adquery/AdQueryBidderTest.java | 69 +++-------------- 5 files changed, 54 insertions(+), 110 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java index 8bad310c80b..4e00f64b0c8 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java +++ b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java @@ -1,6 +1,5 @@ package org.prebid.server.bidder.adquery; -import com.fasterxml.jackson.core.io.BigDecimalParser; import com.fasterxml.jackson.core.type.TypeReference; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; @@ -13,6 +12,7 @@ import io.vertx.core.MultiMap; import io.vertx.core.http.HttpMethod; 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.adquery.model.request.AdQueryRequest; @@ -38,6 +38,8 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; public class AdQueryBidder implements Bidder { @@ -99,25 +101,25 @@ private HttpRequest createRequest(BidRequest bidRequest, Imp imp } private AdQueryRequest createAdQueryRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { + final Optional optionalDevice = Optional.ofNullable(bidRequest.getDevice()); return AdQueryRequest.builder() .v(PREBID_VERSION) .placementCode(extImpAdQuery.getPlacementId()) .auctionId(StringUtils.EMPTY) .type(extImpAdQuery.getType()) .adUnitCode(imp.getTagid()) - .bidQid(ObjectUtil.getIfNotNullOrDefault(bidRequest.getUser(), User::getId, () -> StringUtils.EMPTY)) - .bidId(String.format("%s%s", bidRequest.getId(), imp.getId())) + .bidQid(Optional.ofNullable(bidRequest.getUser()).map(User::getId).orElse(StringUtils.EMPTY)) + .bidId(bidRequest.getId() + imp.getId()) .bidder(BIDDER_NAME) .bidderRequestId(bidRequest.getId()) .bidRequestsCount(1) .bidderRequestsCount(1) .sizes(getImpSizes(imp)) - .bidIp(ObjectUtil.getIfNotNull(bidRequest.getDevice(), Device::getIp)) - .bidIpv6(ObjectUtil.getIfNotNull(bidRequest.getDevice(), Device::getIpv6)) - .bidUa(ObjectUtil.getIfNotNull(bidRequest.getDevice(), Device::getUa)) - .bidPageUrl(ObjectUtil.getIfNotNull(bidRequest.getSite(), Site::getPage)) + .bidIp(optionalDevice.map(Device::getIp).orElse(null)) + .bidIpv6(optionalDevice.map(Device::getIpv6).orElse(null)) + .bidUa(optionalDevice.map(Device::getUa).orElse(null)) + .bidPageUrl(Optional.ofNullable(bidRequest.getSite()).map(Site::getPage).orElse(null)) .build(); - } private String getImpSizes(Imp imp) { @@ -128,10 +130,11 @@ private String getImpSizes(Imp imp) { final List format = banner.getFormat(); if (CollectionUtils.isNotEmpty(format)) { - final List sizes = new ArrayList<>(); - format.forEach(singleFormat -> sizes.add( - "%sx%s".formatted(getIntOrElseZero(singleFormat.getW()), getIntOrElseZero(singleFormat.getH())))); - return String.join("_", sizes); + return format.stream() + .map(singleFormat -> "%sx%s".formatted( + ObjectUtils.defaultIfNull(singleFormat.getW(), 0), + ObjectUtils.defaultIfNull(singleFormat.getH(), 0))) + .collect(Collectors.joining("_")); } final Integer w = banner.getW(); @@ -143,17 +146,14 @@ private String getImpSizes(Imp imp) { return StringUtils.EMPTY; } - private int getIntOrElseZero(Integer number) { - return number != null ? number : 0; - } - private MultiMap resolveHeader(Device device) { final MultiMap headers = HttpUtil.headers(); headers.add(HttpUtil.X_OPENRTB_VERSION_HEADER, ORTB_VERSION); - if (Objects.nonNull(device) && StringUtils.isNotBlank(device.getIp()) && device.getIp().length() > 0) { - headers.add(HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp()); - } + Optional.ofNullable(device) + .map(Device::getIp) + .map(StringUtils::isNotBlank) + .ifPresent(ip -> headers.add(HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp())); return headers; } @@ -170,44 +170,36 @@ public final Result> makeBids(BidderCall httpCal } private static List extractBids(AdQueryResponse adQueryResponse, BidRequest bidRequest) { - if (adQueryResponse == null || Objects.isNull(adQueryResponse.getData())) { + if (adQueryResponse == null || adQueryResponse.getData() == null) { return Collections.emptyList(); } final AdQueryDataResponse data = adQueryResponse.getData(); final Bid bid = Bid.builder() .id(data.getRequestId()) - .impid(resoleImpId(bidRequest, data)) - .price(BigDecimalParser.parse(data.getCpm())) - .adm(String.format(ADM_TEMPLATE, data.getAdqLib(), data.getTag())) + .impid(resolveImpId(bidRequest, data)) + .price(data.getCpm()) + .adm(ADM_TEMPLATE.formatted(data.getAdqLib(), data.getTag())) .adomain(data.getAdDomains()) - .crid(String.format("%d", data.getCreationId())) - .w(parseMeasure(ObjectUtil.getIfNotNull(data.getAdQueryMediaType(), AdQueryMediaType::getWidth))) - .h(parseMeasure(ObjectUtil.getIfNotNull(data.getAdQueryMediaType(), AdQueryMediaType::getHeight))) + .crid(data.getCreationId()) + .w(ObjectUtil.getIfNotNull(data.getAdQueryMediaType(), AdQueryMediaType::getWidth)) + .h(ObjectUtil.getIfNotNull(data.getAdQueryMediaType(), AdQueryMediaType::getHeight)) .build(); - return Collections.singletonList(BidderBid.of(bid, resolveMediaType(data), + return Collections.singletonList(BidderBid.of(bid, resolveMediaType(data.getAdQueryMediaType()), StringUtils.isNotBlank(data.getCurrency()) ? data.getCurrency() : DEFAULT_CURRENCY)); } - private static String resoleImpId(BidRequest bidRequest, AdQueryDataResponse data) { - return Objects.nonNull(data.getRequestId()) + private static String resolveImpId(BidRequest bidRequest, AdQueryDataResponse data) { + return data.getRequestId() != null ? bidRequest.getId().replaceAll(data.getRequestId(), StringUtils.EMPTY) : bidRequest.getId(); } - private static Integer parseMeasure(String measure) { - try { - return Integer.valueOf(measure); - } catch (NumberFormatException e) { - throw new PreBidException("Value of measure: %s can not be parsed.".formatted(measure)); - } - } - - private static BidType resolveMediaType(AdQueryDataResponse data) { - if (data.getAdQueryMediaType().getName() != BidType.banner) { - throw new PreBidException(String.format("Unsupported MediaType: %s", data.getAdQueryMediaType().getName())); + private static BidType resolveMediaType(AdQueryMediaType mediaType) { + if (mediaType.getName() != BidType.banner) { + throw new PreBidException(String.format("Unsupported MediaType: %s", mediaType.getName())); } - return data.getAdQueryMediaType().getName(); + return BidType.banner; } } diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java b/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java index 06d9e4c5bb8..83902c72751 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java +++ b/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java @@ -2,12 +2,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; +import lombok.Value; @Builder(toBuilder = true) -@EqualsAndHashCode -@Getter +@Value public class AdQueryRequest { String v; @@ -20,7 +18,7 @@ public class AdQueryRequest { String type; - @JsonProperty("AdUnitCode") + @JsonProperty("adUnitCode") String adUnitCode; @JsonProperty("bidQid") @@ -38,7 +36,6 @@ public class AdQueryRequest { @JsonProperty("bidUa") String bidUa; - @JsonProperty("bidder") String bidder; @JsonProperty("bidPageUrl") diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java index d51985daa7c..527dc46d799 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java +++ b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; +import java.math.BigDecimal; import java.util.List; @Builder(toBuilder = true) @@ -14,11 +15,11 @@ public class AdQueryDataResponse { String requestId; @JsonProperty("creationId") - Integer creationId; + String creationId; String currency; - String cpm; + BigDecimal cpm; String code; @@ -30,7 +31,8 @@ public class AdQueryDataResponse { @JsonProperty("adDomains") List adDomains; - String deadlid; + @JsonProperty("dealid") + String dealId; @JsonProperty("mediaType") AdQueryMediaType adQueryMediaType; diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryMediaType.java b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryMediaType.java index 76b4b2257ec..d17505fcb9d 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryMediaType.java +++ b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryMediaType.java @@ -8,7 +8,7 @@ public class AdQueryMediaType { BidType name; - String width; + Integer width; - String height; + Integer height; } diff --git a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java index cabef2b6258..06efdf936d8 100644 --- a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java @@ -278,8 +278,9 @@ public void makeBidsShouldReturnErrorWhenAdQueryResponseMediaTypeDoesNotContainB final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() .requestId("abs") - .cpm("123") - .adQueryMediaType(AdQueryMediaType.of(BidType.audio, "120", "320")) + .cpm(BigDecimal.valueOf(123)) + .adQueryMediaType(AdQueryMediaType.of(BidType.audio, 120, + 320)) .build()))); // when @@ -301,12 +302,12 @@ public void makeBidsShouldReturnCorrectResponseBody() final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() .requestId("abs") - .cpm("123") + .cpm(BigDecimal.valueOf(123)) .adqLib("AnyLib") .tag("AnyTag") .adDomains(singletonList("any")) - .creationId(312) - .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "120", "320")) + .creationId("312") + .adQueryMediaType(AdQueryMediaType.of(BidType.banner, 120, 320)) .build()))); // when @@ -337,54 +338,6 @@ public void makeBidsShouldReturnCorrectResponseBody() assertThat(result.getErrors()).isEmpty(); } - @Test - public void makeBidsShouldReturnThrowErrorWheResponseBodyAdQueryMediaTypeWidthIsInvalid() - throws JsonProcessingException { - // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() - .requestId("abs") - .cpm("123") - .adqLib("AnyLib") - .tag("AnyTag") - .adDomains(singletonList("any")) - .creationId(312) - .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "ab", "320")) - .build()))); - - // when - final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); - - // then - assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badServerResponse("Value of measure: ab can not be parsed.")); - } - - @Test - public void makeBidsShouldReturnThrowErrorWheResponseBodyAdQueryMediaTypeHeightIsInvalid() - throws JsonProcessingException { - // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() - .requestId("abs") - .cpm("123") - .adqLib("AnyLib") - .tag("AnyTag") - .adDomains(singletonList("any")) - .creationId(312) - .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "312", "as")) - .build()))); - - // when - final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); - - // then - assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badServerResponse("Value of measure: as can not be parsed.")); - } - @Test public void makeBidsShouldCorrectPopulateCurrencyWhenCurrencyInResponseSpecified() throws JsonProcessingException { @@ -392,13 +345,13 @@ public void makeBidsShouldCorrectPopulateCurrencyWhenCurrencyInResponseSpecified final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() .requestId("abs") - .cpm("321") + .cpm(BigDecimal.valueOf(123)) .adqLib("AnyLib") .currency("UAH") .tag("AnyTag") .adDomains(singletonList("any")) - .creationId(312) - .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "312", "321")) + .creationId("312") + .adQueryMediaType(AdQueryMediaType.of(BidType.banner, 312, 312)) .build()))); // when @@ -419,12 +372,12 @@ public void makeBidsShouldUseDefaultCurrencyWhenCurrencyInResponseDoesNotSpecifi final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() .requestId("abs") - .cpm("321") + .cpm(BigDecimal.valueOf(321)) .adqLib("AnyLib") .currency(null) .tag("AnyTag") .adDomains(singletonList("any")) - .adQueryMediaType(AdQueryMediaType.of(BidType.banner, "312", "321")) + .adQueryMediaType(AdQueryMediaType.of(BidType.banner, 312, 321)) .build()))); // when From a30a8688907a7a7ccc9e150c1f2990d2a1465928 Mon Sep 17 00:00:00 2001 From: markiian Date: Wed, 31 Jan 2024 19:07:50 +0200 Subject: [PATCH 3/7] Update after review --- .../model/response/AdQueryDataResponse.java | 4 +- .../bidder/adquery/AdQueryBidderTest.java | 74 ++++++++----------- 2 files changed, 31 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java index 527dc46d799..a7bb6b85609 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java +++ b/src/main/java/org/prebid/server/bidder/adquery/model/response/AdQueryDataResponse.java @@ -2,13 +2,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; -import lombok.Getter; +import lombok.Value; import java.math.BigDecimal; import java.util.List; @Builder(toBuilder = true) -@Getter +@Value(staticConstructor = "of") public class AdQueryDataResponse { @JsonProperty("requestId") diff --git a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java index 06efdf936d8..266dde79748 100644 --- a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java @@ -229,7 +229,7 @@ public void makeHttpRequestsShouldPopulateSizesFieldWithEmptyStringWhenBannerNul @Test public void makeBidsShouldReturnErrorIfBidResponseBodyCouldNotBeParsed() { // given - final BidderCall httpCall = givenHttpCall(null, "invalid"); + final BidderCall httpCall = givenHttpCall("invalid"); // when final Result> result = target.makeBids(httpCall, null); @@ -246,8 +246,7 @@ public void makeBidsShouldReturnErrorIfBidResponseBodyCouldNotBeParsed() { @Test public void makeBidsShouldReturnEmptyListIfAdQueryResponseIsNull() 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); @@ -260,8 +259,7 @@ public void makeBidsShouldReturnEmptyListIfAdQueryResponseIsNull() throws JsonPr @Test public void makeBidsShouldReturnEmptyListIfAdQueryRequestDataIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(AdQueryResponse.of(null))); + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(AdQueryResponse.of(null))); // when final Result> result = target.makeBids(httpCall, null); @@ -275,7 +273,7 @@ public void makeBidsShouldReturnEmptyListIfAdQueryRequestDataIsNull() throws Jso public void makeBidsShouldReturnErrorWhenAdQueryResponseMediaTypeDoesNotContainBannerType() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, + final BidderCall httpCall = givenHttpCall( mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() .requestId("abs") .cpm(BigDecimal.valueOf(123)) @@ -299,16 +297,7 @@ public void makeBidsShouldReturnErrorWhenAdQueryResponseMediaTypeDoesNotContainB public void makeBidsShouldReturnCorrectResponseBody() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() - .requestId("abs") - .cpm(BigDecimal.valueOf(123)) - .adqLib("AnyLib") - .tag("AnyTag") - .adDomains(singletonList("any")) - .creationId("312") - .adQueryMediaType(AdQueryMediaType.of(BidType.banner, 120, 320)) - .build()))); + final BidderCall httpCall = givenHttpCall(identity()); // when final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); @@ -317,7 +306,7 @@ public void makeBidsShouldReturnCorrectResponseBody() assertThat(result.getValue()) .extracting(BidderBid::getBidCurrency) .hasSize(1) - .containsExactly("PLN"); + .containsExactly("UAH"); assertThat(result.getValue()) .extracting(BidderBid::getType) .hasSize(1) @@ -328,7 +317,7 @@ public void makeBidsShouldReturnCorrectResponseBody() .containsExactly(Bid.builder() .id("abs") .impid("1") - .price(BigDecimal.valueOf(123)) + .price(BigDecimal.valueOf(321)) .adm("AnyTag") .adomain(singletonList("any")) .crid("312") @@ -342,17 +331,7 @@ public void makeBidsShouldReturnCorrectResponseBody() public void makeBidsShouldCorrectPopulateCurrencyWhenCurrencyInResponseSpecified() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() - .requestId("abs") - .cpm(BigDecimal.valueOf(123)) - .adqLib("AnyLib") - .currency("UAH") - .tag("AnyTag") - .adDomains(singletonList("any")) - .creationId("312") - .adQueryMediaType(AdQueryMediaType.of(BidType.banner, 312, 312)) - .build()))); + final BidderCall httpCall = givenHttpCall(identity()); // when final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); @@ -369,16 +348,8 @@ public void makeBidsShouldCorrectPopulateCurrencyWhenCurrencyInResponseSpecified public void makeBidsShouldUseDefaultCurrencyWhenCurrencyInResponseDoesNotSpecified() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() - .requestId("abs") - .cpm(BigDecimal.valueOf(321)) - .adqLib("AnyLib") - .currency(null) - .tag("AnyTag") - .adDomains(singletonList("any")) - .adQueryMediaType(AdQueryMediaType.of(BidType.banner, 312, 321)) - .build()))); + final BidderCall httpCall = givenHttpCall( + adQueryDataResponse -> adQueryDataResponse.currency(null)); // when final Result> result = target.makeBids(httpCall, BidRequest.builder().id("abs1").build()); @@ -420,14 +391,27 @@ private static Imp givenImp(UnaryOperator impCustomizer) { .build(); } - private static BidderCall givenHttpCall(String body) { + private static BidderCall givenHttpCall(String body) { return BidderCall.succeededHttp(null, HttpResponse.of(200, null, body), null); } - private static BidderCall givenHttpCall(AdQueryRequest adQueryRequest, String body) { - return BidderCall.succeededHttp( - HttpRequest.builder().payload(adQueryRequest).build(), - HttpResponse.of(200, null, body), - null); + private static BidderCall givenHttpCall( + UnaryOperator adQueryResponse) + throws JsonProcessingException { + final String body = mapper.writeValueAsString( + AdQueryResponse.of( + adQueryResponse.apply( + AdQueryDataResponse.builder() + .requestId("abs") + .cpm(BigDecimal.valueOf(321)) + .creationId("312") + .adqLib("AnyLib") + .currency("UAH") + .tag("AnyTag") + .adDomains(singletonList("any")) + .adQueryMediaType( + AdQueryMediaType.of(BidType.banner, 120, 320))) + .build())); + return BidderCall.succeededHttp(null, HttpResponse.of(200, null, body), null); } } From 53930d09265b66c4747716a7fb120e0d6a32db12 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Mon, 5 Feb 2024 14:18:32 +0100 Subject: [PATCH 4/7] Fix comments --- .../server/bidder/adquery/AdQueryBidder.java | 126 +++++++++--------- .../bidder/adquery/AdQueryBidderTest.java | 40 +++--- 2 files changed, 82 insertions(+), 84 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java index 4e00f64b0c8..b4c7addbc7c 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java +++ b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java @@ -30,7 +30,6 @@ import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.adquery.ExtImpAdQuery; import org.prebid.server.proto.openrtb.ext.response.BidType; -import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; import org.prebid.server.util.ObjectUtil; @@ -51,6 +50,7 @@ public class AdQueryBidder implements Bidder { private static final String DEFAULT_CURRENCY = "PLN"; private static final String ORTB_VERSION = "2.5"; private static final String ADM_TEMPLATE = "%s"; + private static final String FORMAT_TEMPLATE = "%sx%s"; private final String endpointUrl; private final JacksonMapper mapper; @@ -87,17 +87,15 @@ private ExtImpAdQuery parseImpExt(Imp imp) { } } - private HttpRequest createRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { - final AdQueryRequest outgoingRequest = createAdQueryRequest(bidRequest, imp, extImpAdQuery); - - return HttpRequest.builder() - .method(HttpMethod.POST) - .uri(endpointUrl) - .headers(resolveHeader(bidRequest.getDevice())) - .impIds(BidderUtil.impIds(bidRequest)) - .payload(outgoingRequest) - .body(mapper.encodeToBytes(outgoingRequest)) - .build(); + @Override + public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final AdQueryResponse bidResponse = mapper.decodeValue( + httpCall.getResponse().getBody(), AdQueryResponse.class); + return Result.withValues(extractBids(bidResponse, bidRequest.getId())); + } catch (DecodeException | PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } } private AdQueryRequest createAdQueryRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { @@ -122,6 +120,55 @@ private AdQueryRequest createAdQueryRequest(BidRequest bidRequest, Imp imp, ExtI .build(); } + private static List extractBids(AdQueryResponse adQueryResponse, String bidRequestId) { + if (adQueryResponse == null || adQueryResponse.getData() == null) { + return Collections.emptyList(); + } + + final AdQueryDataResponse data = adQueryResponse.getData(); + final AdQueryMediaType mediaType = data.getAdQueryMediaType(); + final Bid bid = Bid.builder() + .id(data.getRequestId()) + .impid(resolveImpId(bidRequestId, data.getRequestId())) + .price(data.getCpm()) + .adm(ADM_TEMPLATE.formatted(data.getAdqLib(), data.getTag())) + .adomain(data.getAdDomains()) + .crid(data.getCreationId()) + .w(ObjectUtil.getIfNotNull(mediaType, AdQueryMediaType::getWidth)) + .h(ObjectUtil.getIfNotNull(mediaType, AdQueryMediaType::getHeight)) + .build(); + + final BidType bidType = ObjectUtil.getIfNotNull(mediaType, AdQueryMediaType::getName); + final String currency = StringUtils.isNotBlank(data.getCurrency()) ? data.getCurrency() : DEFAULT_CURRENCY; + return Collections.singletonList(BidderBid.of(bid, resolveMediaType(bidType), currency)); + } + + private static String resolveImpId(String bidRequestId, String adQueryRequestId) { + return adQueryRequestId != null + ? bidRequestId.replaceAll(adQueryRequestId, StringUtils.EMPTY) + : bidRequestId; + } + + private static BidType resolveMediaType(BidType bidType) { + if (bidType != BidType.banner) { + throw new PreBidException("Unsupported MediaType: %s".formatted(bidType)); + } + return BidType.banner; + } + + private HttpRequest createRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { + final AdQueryRequest outgoingRequest = createAdQueryRequest(bidRequest, imp, extImpAdQuery); + + return HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpointUrl) + .headers(makeHeaders(bidRequest.getDevice())) + .impIds(Collections.singleton(imp.getId())) + .payload(outgoingRequest) + .body(mapper.encodeToBytes(outgoingRequest)) + .build(); + } + private String getImpSizes(Imp imp) { final Banner banner = imp.getBanner(); if (banner == null) { @@ -131,7 +178,7 @@ private String getImpSizes(Imp imp) { final List format = banner.getFormat(); if (CollectionUtils.isNotEmpty(format)) { return format.stream() - .map(singleFormat -> "%sx%s".formatted( + .map(singleFormat -> FORMAT_TEMPLATE.formatted( ObjectUtils.defaultIfNull(singleFormat.getW(), 0), ObjectUtils.defaultIfNull(singleFormat.getH(), 0))) .collect(Collectors.joining("_")); @@ -140,66 +187,21 @@ private String getImpSizes(Imp imp) { final Integer w = banner.getW(); final Integer h = banner.getH(); if (w != null && h != null) { - return "%sx%s".formatted(w, h); + return FORMAT_TEMPLATE.formatted(w, h); } return StringUtils.EMPTY; } - private MultiMap resolveHeader(Device device) { + private MultiMap makeHeaders(Device device) { final MultiMap headers = HttpUtil.headers(); headers.add(HttpUtil.X_OPENRTB_VERSION_HEADER, ORTB_VERSION); Optional.ofNullable(device) .map(Device::getIp) - .map(StringUtils::isNotBlank) - .ifPresent(ip -> headers.add(HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp())); + .filter(StringUtils::isNotBlank) + .ifPresent(ip -> headers.add(HttpUtil.X_FORWARDED_FOR_HEADER, ip)); return headers; } - - @Override - public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { - try { - final AdQueryResponse bidResponse = mapper.decodeValue( - httpCall.getResponse().getBody(), AdQueryResponse.class); - return Result.withValues(extractBids(bidResponse, bidRequest)); - } catch (DecodeException | PreBidException e) { - return Result.withError(BidderError.badServerResponse(e.getMessage())); - } - } - - private static List extractBids(AdQueryResponse adQueryResponse, BidRequest bidRequest) { - if (adQueryResponse == null || adQueryResponse.getData() == null) { - return Collections.emptyList(); - } - - final AdQueryDataResponse data = adQueryResponse.getData(); - final Bid bid = Bid.builder() - .id(data.getRequestId()) - .impid(resolveImpId(bidRequest, data)) - .price(data.getCpm()) - .adm(ADM_TEMPLATE.formatted(data.getAdqLib(), data.getTag())) - .adomain(data.getAdDomains()) - .crid(data.getCreationId()) - .w(ObjectUtil.getIfNotNull(data.getAdQueryMediaType(), AdQueryMediaType::getWidth)) - .h(ObjectUtil.getIfNotNull(data.getAdQueryMediaType(), AdQueryMediaType::getHeight)) - .build(); - - return Collections.singletonList(BidderBid.of(bid, resolveMediaType(data.getAdQueryMediaType()), - StringUtils.isNotBlank(data.getCurrency()) ? data.getCurrency() : DEFAULT_CURRENCY)); - } - - private static String resolveImpId(BidRequest bidRequest, AdQueryDataResponse data) { - return data.getRequestId() != null - ? bidRequest.getId().replaceAll(data.getRequestId(), StringUtils.EMPTY) - : bidRequest.getId(); - } - - private static BidType resolveMediaType(AdQueryMediaType mediaType) { - if (mediaType.getName() != BidType.banner) { - throw new PreBidException(String.format("Unsupported MediaType: %s", mediaType.getName())); - } - return BidType.banner; - } } diff --git a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java index 266dde79748..ca226f0faff 100644 --- a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java @@ -93,9 +93,8 @@ public void makeHttpRequestsShouldReturnErrorsOfNotValidImps() { final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).hasSize(1); - assertThat(result.getErrors().get(0).getMessage()) - .startsWith("Cannot deserialize value of type "); + assertThat(result.getErrors()).hasSize(1).first() + .satisfies(error -> assertThat(error.getMessage()).startsWith("Cannot deserialize value of type")); } @Test @@ -249,7 +248,7 @@ public void makeBidsShouldReturnEmptyListIfAdQueryResponseIsNull() throws JsonPr final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(null)); // when - final Result> result = target.makeBids(httpCall, null); + final Result> result = target.makeBids(httpCall, BidRequest.builder().id("requestId").build()); // then assertThat(result.getErrors()).isEmpty(); @@ -262,7 +261,7 @@ public void makeBidsShouldReturnEmptyListIfAdQueryRequestDataIsNull() throws Jso final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(AdQueryResponse.of(null))); // when - final Result> result = target.makeBids(httpCall, null); + final Result> result = target.makeBids(httpCall, BidRequest.builder().id("requestId").build()); // then assertThat(result.getErrors()).isEmpty(); @@ -277,8 +276,7 @@ public void makeBidsShouldReturnErrorWhenAdQueryResponseMediaTypeDoesNotContainB mapper.writeValueAsString(AdQueryResponse.of(AdQueryDataResponse.builder() .requestId("abs") .cpm(BigDecimal.valueOf(123)) - .adQueryMediaType(AdQueryMediaType.of(BidType.audio, 120, - 320)) + .adQueryMediaType(AdQueryMediaType.of(BidType.audio, 120, 320)) .build()))); // when @@ -396,22 +394,20 @@ private static BidderCall givenHttpCall(String body) { } private static BidderCall givenHttpCall( - UnaryOperator adQueryResponse) + UnaryOperator adQueryResponseBuilder) throws JsonProcessingException { - final String body = mapper.writeValueAsString( - AdQueryResponse.of( - adQueryResponse.apply( - AdQueryDataResponse.builder() - .requestId("abs") - .cpm(BigDecimal.valueOf(321)) - .creationId("312") - .adqLib("AnyLib") - .currency("UAH") - .tag("AnyTag") - .adDomains(singletonList("any")) - .adQueryMediaType( - AdQueryMediaType.of(BidType.banner, 120, 320))) - .build())); + + final AdQueryDataResponse adQueryResponse = adQueryResponseBuilder.apply(AdQueryDataResponse.builder() + .requestId("abs") + .cpm(BigDecimal.valueOf(321)) + .creationId("312") + .adqLib("AnyLib") + .currency("UAH") + .tag("AnyTag") + .adDomains(singletonList("any")) + .adQueryMediaType(AdQueryMediaType.of(BidType.banner, 120, 320))) + .build(); + final String body = mapper.writeValueAsString(AdQueryResponse.of(adQueryResponse)); return BidderCall.succeededHttp(null, HttpResponse.of(200, null, body), null); } } From 49011271b748fc8224c8527578aa520076eaff4b Mon Sep 17 00:00:00 2001 From: antonbabak Date: Wed, 7 Feb 2024 09:01:22 +0100 Subject: [PATCH 5/7] Fix comments --- .../server/bidder/adquery/AdQueryBidder.java | 120 +++++++++--------- .../bidder/adquery/AdQueryBidderTest.java | 2 +- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java index b4c7addbc7c..17a7cb98eef 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java +++ b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java @@ -87,18 +87,20 @@ private ExtImpAdQuery parseImpExt(Imp imp) { } } - @Override - public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { - try { - final AdQueryResponse bidResponse = mapper.decodeValue( - httpCall.getResponse().getBody(), AdQueryResponse.class); - return Result.withValues(extractBids(bidResponse, bidRequest.getId())); - } catch (DecodeException | PreBidException e) { - return Result.withError(BidderError.badServerResponse(e.getMessage())); - } + private HttpRequest createRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { + final AdQueryRequest outgoingRequest = createAdQueryRequest(bidRequest, imp, extImpAdQuery); + + return HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpointUrl) + .headers(makeHeaders(bidRequest.getDevice())) + .impIds(Collections.singleton(imp.getId())) + .payload(outgoingRequest) + .body(mapper.encodeToBytes(outgoingRequest)) + .build(); } - private AdQueryRequest createAdQueryRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { + private static AdQueryRequest createAdQueryRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { final Optional optionalDevice = Optional.ofNullable(bidRequest.getDevice()); return AdQueryRequest.builder() .v(PREBID_VERSION) @@ -107,7 +109,7 @@ private AdQueryRequest createAdQueryRequest(BidRequest bidRequest, Imp imp, ExtI .type(extImpAdQuery.getType()) .adUnitCode(imp.getTagid()) .bidQid(Optional.ofNullable(bidRequest.getUser()).map(User::getId).orElse(StringUtils.EMPTY)) - .bidId(bidRequest.getId() + imp.getId()) + .bidId(StringUtils.defaultString(bidRequest.getId()) + StringUtils.defaultString(imp.getId())) .bidder(BIDDER_NAME) .bidderRequestId(bidRequest.getId()) .bidRequestsCount(1) @@ -120,6 +122,53 @@ private AdQueryRequest createAdQueryRequest(BidRequest bidRequest, Imp imp, ExtI .build(); } + private static String getImpSizes(Imp imp) { + final Banner banner = imp.getBanner(); + if (banner == null) { + return StringUtils.EMPTY; + } + + final List format = banner.getFormat(); + if (CollectionUtils.isNotEmpty(format)) { + return format.stream() + .map(singleFormat -> FORMAT_TEMPLATE.formatted( + ObjectUtils.defaultIfNull(singleFormat.getW(), 0), + ObjectUtils.defaultIfNull(singleFormat.getH(), 0))) + .collect(Collectors.joining(",")); + } + + final Integer w = banner.getW(); + final Integer h = banner.getH(); + if (w != null && h != null) { + return FORMAT_TEMPLATE.formatted(w, h); + } + + return StringUtils.EMPTY; + } + + private static MultiMap makeHeaders(Device device) { + final MultiMap headers = HttpUtil.headers(); + headers.add(HttpUtil.X_OPENRTB_VERSION_HEADER, ORTB_VERSION); + + Optional.ofNullable(device) + .map(Device::getIp) + .filter(StringUtils::isNotBlank) + .ifPresent(ip -> headers.add(HttpUtil.X_FORWARDED_FOR_HEADER, ip)); + + return headers; + } + + @Override + public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final AdQueryResponse bidResponse = mapper.decodeValue( + httpCall.getResponse().getBody(), AdQueryResponse.class); + return Result.withValues(extractBids(bidResponse, bidRequest.getId())); + } catch (DecodeException | PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + private static List extractBids(AdQueryResponse adQueryResponse, String bidRequestId) { if (adQueryResponse == null || adQueryResponse.getData() == null) { return Collections.emptyList(); @@ -155,53 +204,4 @@ private static BidType resolveMediaType(BidType bidType) { } return BidType.banner; } - - private HttpRequest createRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { - final AdQueryRequest outgoingRequest = createAdQueryRequest(bidRequest, imp, extImpAdQuery); - - return HttpRequest.builder() - .method(HttpMethod.POST) - .uri(endpointUrl) - .headers(makeHeaders(bidRequest.getDevice())) - .impIds(Collections.singleton(imp.getId())) - .payload(outgoingRequest) - .body(mapper.encodeToBytes(outgoingRequest)) - .build(); - } - - private String getImpSizes(Imp imp) { - final Banner banner = imp.getBanner(); - if (banner == null) { - return StringUtils.EMPTY; - } - - final List format = banner.getFormat(); - if (CollectionUtils.isNotEmpty(format)) { - return format.stream() - .map(singleFormat -> FORMAT_TEMPLATE.formatted( - ObjectUtils.defaultIfNull(singleFormat.getW(), 0), - ObjectUtils.defaultIfNull(singleFormat.getH(), 0))) - .collect(Collectors.joining("_")); - } - - final Integer w = banner.getW(); - final Integer h = banner.getH(); - if (w != null && h != null) { - return FORMAT_TEMPLATE.formatted(w, h); - } - - return StringUtils.EMPTY; - } - - private MultiMap makeHeaders(Device device) { - final MultiMap headers = HttpUtil.headers(); - headers.add(HttpUtil.X_OPENRTB_VERSION_HEADER, ORTB_VERSION); - - Optional.ofNullable(device) - .map(Device::getIp) - .filter(StringUtils::isNotBlank) - .ifPresent(ip -> headers.add(HttpUtil.X_FORWARDED_FOR_HEADER, ip)); - - return headers; - } } diff --git a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java index ca226f0faff..dceeb1f53f0 100644 --- a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java @@ -79,7 +79,7 @@ public void makeHttpRequestsShouldCorrectPopulateAdQueryRequest() { .bidderRequestId("22e26bd9a702bc") .bidderRequestsCount(1) .bidRequestsCount(1) - .sizes("320x100_300x250") + .sizes("320x100,300x250") .build()); } From 6d7efd3c7823d45eaff4309bbd8437c0205f8fcf Mon Sep 17 00:00:00 2001 From: antonbabak Date: Wed, 7 Feb 2024 09:02:19 +0100 Subject: [PATCH 6/7] Fix comments --- .../server/bidder/adquery/model/request/AdQueryRequest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java b/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java index 83902c72751..625cf8a1cb0 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java +++ b/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java @@ -8,6 +8,7 @@ @Value public class AdQueryRequest { + @JsonProperty("v") String v; @JsonProperty("placementCode") From 4fc3bf119af47abe73943065b4cd4cdd6348fcb5 Mon Sep 17 00:00:00 2001 From: serhiinahornyi Date: Wed, 7 Feb 2024 12:15:15 +0200 Subject: [PATCH 7/7] Fix field name --- .../java/org/prebid/server/bidder/adquery/AdQueryBidder.java | 2 +- .../server/bidder/adquery/model/request/AdQueryRequest.java | 2 +- .../org/prebid/server/bidder/adquery/AdQueryBidderTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java index 17a7cb98eef..40416c80db0 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java +++ b/src/main/java/org/prebid/server/bidder/adquery/AdQueryBidder.java @@ -103,7 +103,7 @@ private HttpRequest createRequest(BidRequest bidRequest, Imp imp private static AdQueryRequest createAdQueryRequest(BidRequest bidRequest, Imp imp, ExtImpAdQuery extImpAdQuery) { final Optional optionalDevice = Optional.ofNullable(bidRequest.getDevice()); return AdQueryRequest.builder() - .v(PREBID_VERSION) + .version(PREBID_VERSION) .placementCode(extImpAdQuery.getPlacementId()) .auctionId(StringUtils.EMPTY) .type(extImpAdQuery.getType()) diff --git a/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java b/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java index 625cf8a1cb0..cf548d880cb 100644 --- a/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java +++ b/src/main/java/org/prebid/server/bidder/adquery/model/request/AdQueryRequest.java @@ -9,7 +9,7 @@ public class AdQueryRequest { @JsonProperty("v") - String v; + String version; @JsonProperty("placementCode") String placementCode; diff --git a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java index dceeb1f53f0..4d32d1da91e 100644 --- a/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/adquery/AdQueryBidderTest.java @@ -64,7 +64,7 @@ public void makeHttpRequestsShouldCorrectPopulateAdQueryRequest() { assertThat(result.getValue()).hasSize(1) .extracting(HttpRequest::getPayload) .containsExactly(AdQueryRequest.builder() - .v("server") + .version("server") .placementCode("pl1") .auctionId("") .type("6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897")