From 171c53ef0585de53a12d31ac39f511abe43e6ba9 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 17 Aug 2023 11:35:57 +0200 Subject: [PATCH 1/2] Add the GothamAds Bidder --- .../bidder/gotthamads/GothamAdsBidder.java | 151 +++++++++++++ .../request/gothamads/GothamAdsImpExt.java | 12 + .../config/bidder/GothamAdsConfiguration.java | 41 ++++ .../resources/bidder-config/gothamads.yaml | 15 ++ .../static/bidder-params/gothamads.json | 16 ++ .../bidder/gothamads/GothamAdsBidderTest.java | 211 ++++++++++++++++++ .../org/prebid/server/it/GothamAdsTest.java | 38 ++++ .../test-auction-gothamads-request.json | 23 ++ .../test-auction-gothamads-response.json | 36 +++ .../gothamads/test-gothamads-bid-request.json | 50 +++++ .../test-gothamads-bid-response.json | 19 ++ .../server/it/test-application.properties | 2 + 12 files changed, 614 insertions(+) create mode 100644 src/main/java/org/prebid/server/bidder/gotthamads/GothamAdsBidder.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/gothamads/GothamAdsImpExt.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/GothamAdsConfiguration.java create mode 100644 src/main/resources/bidder-config/gothamads.yaml create mode 100644 src/main/resources/static/bidder-params/gothamads.json create mode 100644 src/test/java/org/prebid/server/bidder/gothamads/GothamAdsBidderTest.java create mode 100644 src/test/java/org/prebid/server/it/GothamAdsTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-gothamads-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-gothamads-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/gotthamads/GothamAdsBidder.java b/src/main/java/org/prebid/server/bidder/gotthamads/GothamAdsBidder.java new file mode 100644 index 00000000000..e9bcecb10b2 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/gotthamads/GothamAdsBidder.java @@ -0,0 +1,151 @@ +package org.prebid.server.bidder.gotthamads; + +import com.fasterxml.jackson.core.type.TypeReference; +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 io.vertx.core.MultiMap; +import io.vertx.core.http.HttpMethod; +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.gothamads.GothamAdsImpExt; +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.List; +import java.util.Objects; +import java.util.Optional; + +public class GothamAdsBidder implements Bidder { + + private static final TypeReference> TYPE_REFERENCE = new TypeReference<>() { + }; + private static final String ACCOUNT_ID_MACRO = "{{AccountId}}"; + private static final String X_OPENRTB_VERSION = "2.5"; + + private final String endpointUrl; + private final JacksonMapper mapper; + + public GothamAdsBidder(String endpointUrl, JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final GothamAdsImpExt impExt; + final Imp firstImp = request.getImp().get(0); + try { + impExt = parseImpExt(firstImp); + } catch (PreBidException e) { + return Result.withError(BidderError.badInput(e.getMessage())); + } + + final BidRequest bidRequest = cleanUpFirstImpExt(request); + final HttpRequest httpRequest = HttpRequest.builder() + .method(HttpMethod.POST) + .uri(resolveEndpoint(impExt.getAccountId())) + .headers(makeHeaders(request)) + .impIds(BidderUtil.impIds(bidRequest)) + .body(mapper.encodeToBytes(bidRequest)) + .payload(bidRequest) + .build(); + return Result.withValue(httpRequest); + } + + private GothamAdsImpExt parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage()); + } + } + + private static BidRequest cleanUpFirstImpExt(BidRequest request) { + final List imps = new ArrayList<>(request.getImp()); + imps.set(0, request.getImp().get(0).toBuilder().ext(null).build()); + return request.toBuilder().imp(imps).build(); + } + + private String resolveEndpoint(String accountId) { + return endpointUrl.replace(ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(accountId)); + } + + private static MultiMap makeHeaders(BidRequest bidRequest) { + final Device device = bidRequest.getDevice(); + final MultiMap headers = HttpUtil.headers(); + + headers.set(HttpUtil.X_OPENRTB_VERSION_HEADER, X_OPENRTB_VERSION); + HttpUtil.addHeaderIfValueIsNotEmpty( + headers, + HttpUtil.USER_AGENT_HEADER, + ObjectUtil.getIfNotNull(device, Device::getUa) + ); + HttpUtil.addHeaderIfValueIsNotEmpty( + headers, + HttpUtil.X_FORWARDED_FOR_HEADER, + ObjectUtil.getIfNotNull(device, Device::getIpv6) + ); + HttpUtil.addHeaderIfValueIsNotEmpty( + headers, + HttpUtil.X_FORWARDED_FOR_HEADER, + ObjectUtil.getIfNotNull(device, Device::getIp) + ); + + return headers; + } + + @Override + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + final boolean hasNoSeatBids = Optional.ofNullable(bidResponse.getSeatbid()).map(List::isEmpty).orElse(true); + return hasNoSeatBids + ? Result.withError(BidderError.badServerResponse("Empty SeatBid array")) + : Result.withValues(extractBids(bidResponse)); + + } catch (DecodeException e) { + return Result.withError(BidderError.badServerResponse("Bad Server Response")); + } catch (PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private static List extractBids(BidResponse bidResponse) { + return bidResponse.getSeatbid() + .stream() + .flatMap(seatBid -> Optional.ofNullable(seatBid.getBid()).orElse(List.of()).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.video; + case 4 -> BidType.xNative; + default -> throw new PreBidException( + "Unable to fetch mediaType " + bid.getMtype() + " in multi-format: " + bid.getImpid() + ); + }; + } + +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/gothamads/GothamAdsImpExt.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/gothamads/GothamAdsImpExt.java new file mode 100644 index 00000000000..372e6663c45 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/gothamads/GothamAdsImpExt.java @@ -0,0 +1,12 @@ +package org.prebid.server.proto.openrtb.ext.request.gothamads; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class GothamAdsImpExt { + + @JsonProperty("accountId") + String accountId; + +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/GothamAdsConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/GothamAdsConfiguration.java new file mode 100644 index 00000000000..029f9423886 --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/GothamAdsConfiguration.java @@ -0,0 +1,41 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.gotthamads.GothamAdsBidder; +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/gothamads.yaml", factory = YamlPropertySourceFactory.class) +public class GothamAdsConfiguration { + + private static final String BIDDER_NAME = "gothamads"; + + @Bean("gothamAdsConfigurationProperties") + @ConfigurationProperties("adapters.gothamads") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps gothamadsBidderDeps(BidderConfigurationProperties gothamAdsConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(gothamAdsConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new GothamAdsBidder(config.getEndpoint(), mapper)) + .assemble(); + } +} diff --git a/src/main/resources/bidder-config/gothamads.yaml b/src/main/resources/bidder-config/gothamads.yaml new file mode 100644 index 00000000000..bd1e2120945 --- /dev/null +++ b/src/main/resources/bidder-config/gothamads.yaml @@ -0,0 +1,15 @@ +adapters: + gothamads: + endpoint: http://us-e-node1.gothamads.com/?pass={{AccountID}} + meta-info: + maintainer-email: support@gothamads.com + app-media-types: + - banner + - video + - native + site-media-types: + - banner + - video + - native + supported-vendors: + vendor-id: 0 diff --git a/src/main/resources/static/bidder-params/gothamads.json b/src/main/resources/static/bidder-params/gothamads.json new file mode 100644 index 00000000000..3e33513197a --- /dev/null +++ b/src/main/resources/static/bidder-params/gothamads.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Gothamads Adapter Params", + "description": "A schema which validates params accepted by the Gothamads adapter", + "type": "object", + "properties": { + "accountId": { + "type": "string", + "description": "Account id", + "minLength": 1 + } + }, + "required": [ + "accountId" + ] +} diff --git a/src/test/java/org/prebid/server/bidder/gothamads/GothamAdsBidderTest.java b/src/test/java/org/prebid/server/bidder/gothamads/GothamAdsBidderTest.java new file mode 100644 index 00000000000..12b9e46e638 --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/gothamads/GothamAdsBidderTest.java @@ -0,0 +1,211 @@ +package org.prebid.server.bidder.gothamads; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import io.vertx.core.MultiMap; +import io.vertx.core.http.HttpMethod; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.gotthamads.GothamAdsBidder; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderCall; +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.gothamads.GothamAdsImpExt; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +import static java.util.function.Function.identity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.prebid.server.bidder.model.BidderError.Type; +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.video; +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.prebid.server.util.HttpUtil.USER_AGENT_HEADER; +import static org.prebid.server.util.HttpUtil.X_FORWARDED_FOR_HEADER; +import static org.prebid.server.util.HttpUtil.X_OPENRTB_VERSION_HEADER; +import static org.prebid.server.util.HttpUtil.headers; +import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; + +public class GothamAdsBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://test-url.com/?pass={{AccountId}}"; + + private GothamAdsBidder target; + + @BeforeEach + public void setUp() { + target = new GothamAdsBidder(ENDPOINT_URL, jacksonMapper); + } + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException().isThrownBy(() -> new GothamAdsBidder("invalid_url", jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldReturnErrorWhenRequestHasInvalidImpression() { + // 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.getErrors()).hasSize(1).first() + .satisfies(error -> { + assertThat(error.getMessage()).startsWith("Cannot deserialize"); + assertThat(error.getType()).isEqualTo(Type.bad_input); + }); + } + + @Test + public void makeHttpRequestsShouldMakeCorrectRequest() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), + impBuilder -> impBuilder.id("imp_id2").ext(givenImpExt())); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final MultiMap expectedHeaders = headers() + .set(CONTENT_TYPE_HEADER, APPLICATION_JSON_CONTENT_TYPE) + .set(ACCEPT_HEADER, APPLICATION_JSON_VALUE) + .set(X_OPENRTB_VERSION_HEADER, "2.5") + .set(USER_AGENT_HEADER, "ua") + .set(X_FORWARDED_FOR_HEADER, List.of("ipv6", "ip")); + + final BidRequest expectedBidRequest = givenBidRequest( + impBuilder -> impBuilder.ext(null), + impBuilder -> impBuilder.id("imp_id2").ext(givenImpExt())); + final Result>> expectedResult = Result.withValue(HttpRequest.builder() + .method(HttpMethod.POST) + .uri("https://test-url.com/?pass=accountId") + .headers(expectedHeaders) + .impIds(Set.of("imp_id", "imp_id2")) + .body(jacksonMapper.encodeToBytes(expectedBidRequest)) + .payload(expectedBidRequest) + .build() + ); + + assertThat(result.getValue()).usingRecursiveComparison().isEqualTo(expectedResult.getValue()); + assertThat(result.getErrors()).isEmpty(); + } + + @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()).containsExactly(badServerResponse("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 makeBidsShouldReturnBidsSuccessfully() throws JsonProcessingException { + // given + final Bid bannerBid = Bid.builder().impid("1").mtype(1).build(); + final Bid videoBid = Bid.builder().impid("2").mtype(2).build(); + final Bid nativeBid = Bid.builder().impid("3").mtype(4).build(); + + final BidderCall httpCall = givenHttpCall( + givenBidRequest(identity()), + givenBidResponse(bannerBid, videoBid, nativeBid)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsExactly( + BidderBid.of(bannerBid, banner, null), + BidderBid.of(videoBid, video, null), + BidderBid.of(nativeBid, xNative, null)); + } + + @Test + public void makeBidsShouldReturnErrorWhenImpTypeIsNotSupported() throws JsonProcessingException { + // given + final Bid audioBid = Bid.builder().impid("3").mtype(3).build(); + final BidderCall httpCall = givenHttpCall( + givenBidRequest(identity()), + givenBidResponse(audioBid)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()) + .containsExactly(badServerResponse("Unable to fetch mediaType 3 in multi-format: 3")); + } + + private static BidRequest givenBidRequest(Function... impCustomizers) { + return BidRequest.builder() + .device(Device.builder().ua("ua").ip("ip").ipv6("ipv6").build()) + .imp(Arrays.stream(impCustomizers).map(GothamAdsBidderTest::givenImp).toList()) + .build(); + } + + private static Imp givenImp(Function impCustomizer) { + return impCustomizer.apply(Imp.builder().id("imp_id").ext(givenImpExt())).build(); + } + + private static ObjectNode givenImpExt() { + return mapper.valueToTree(ExtPrebid.of(null, GothamAdsImpExt.of("accountId"))); + } + + private static String givenBidResponse(Bid... bids) throws JsonProcessingException { + return mapper.writeValueAsString( + BidResponse.builder() + .seatbid(bids.length == 0 + ? Collections.emptyList() + : List.of(SeatBid.builder().bid(List.of(bids)).build())) + .build() + ); + } + + private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { + return BidderCall.succeededHttp( + HttpRequest.builder().payload(bidRequest).build(), + HttpResponse.of(200, null, body), + null); + } +} diff --git a/src/test/java/org/prebid/server/it/GothamAdsTest.java b/src/test/java/org/prebid/server/it/GothamAdsTest.java new file mode 100644 index 00000000000..339734ab818 --- /dev/null +++ b/src/test/java/org/prebid/server/it/GothamAdsTest.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 java.util.List; + +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; + +@RunWith(SpringRunner.class) +public class GothamAdsTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromGothamAds() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/gothamads-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/gothamads/test-gothamads-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/gothamads/test-gothamads-bid-response.json")))); + + // when + final Response response = responseFor( + "openrtb2/gothamads/test-auction-gothamads-request.json", + Endpoint.openrtb2_auction + ); + + // then + assertJsonEquals("openrtb2/gothamads/test-auction-gothamads-response.json", response, List.of("gothamads")); + } + +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-request.json b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-request.json new file mode 100644 index 00000000000..3661e6a20da --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-request.json @@ -0,0 +1,23 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "ext": { + "gothamads": { + "accountId": "accountid" + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-response.json b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-response.json new file mode 100644 index 00000000000..4554400f4b1 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-auction-gothamads-response.json @@ -0,0 +1,36 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 0.01, + "adid": "2068416", + "cid": "8048", + "crid": "24080", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + }, + "origbidcpm": 0.01 + } + } + ], + "seat": "gothamads", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "gothamads": "{{ gothamads.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-gothamads-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-gothamads-bid-request.json new file mode 100644 index 00000000000..7c09fbd33f7 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-gothamads-bid-request.json @@ -0,0 +1,50 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 320, + "h": 250 + } + } + ], + "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/gothamads/test-gothamads-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-gothamads-bid-response.json new file mode 100644 index 00000000000..47d4f8718ea --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/gothamads/test-gothamads-bid-response.json @@ -0,0 +1,19 @@ +{ + "id": "tid", + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "bid_id", + "impid": "imp_id", + "cid": "8048", + "mtype": 1 + } + ], + "type": "banner" + } + ] +} 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 39d8f433605..8f58a5f12e5 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -397,6 +397,8 @@ adapters.aax.enabled=true adapters.aax.endpoint=http://localhost:8090/aax-exchange adapters.zeta_global_ssp.enabled=true adapters.zeta_global_ssp.endpoint=http://localhost:8090/zeta_global_ssp-exchange +adapters.gothamads.enabled=true +adapters.gothamads.endpoint=http://localhost:8090/gothamads-exchange http-client.circuit-breaker.enabled=true http-client.circuit-breaker.idle-expire-hours=24 http-client.circuit-breaker.opening-threshold=1000 From 81dbe0472b74e5fac53e7217c92c9b180eb70aa4 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 17 Aug 2023 15:46:59 +0200 Subject: [PATCH 2/2] Fixing comments --- .../bidder/gotthamads/GothamAdsBidder.java | 23 ++++++++----------- .../bidder/gothamads/GothamAdsBidderTest.java | 8 +++---- .../server/it/test-application.properties | 4 ++-- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/gotthamads/GothamAdsBidder.java b/src/main/java/org/prebid/server/bidder/gotthamads/GothamAdsBidder.java index e9bcecb10b2..fe2ac533dc4 100644 --- a/src/main/java/org/prebid/server/bidder/gotthamads/GothamAdsBidder.java +++ b/src/main/java/org/prebid/server/bidder/gotthamads/GothamAdsBidder.java @@ -8,6 +8,7 @@ import com.iab.openrtb.response.BidResponse; import io.vertx.core.MultiMap; import io.vertx.core.http.HttpMethod; +import org.apache.commons.collections4.CollectionUtils; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; @@ -92,18 +93,15 @@ private static MultiMap makeHeaders(BidRequest bidRequest) { HttpUtil.addHeaderIfValueIsNotEmpty( headers, HttpUtil.USER_AGENT_HEADER, - ObjectUtil.getIfNotNull(device, Device::getUa) - ); + ObjectUtil.getIfNotNull(device, Device::getUa)); HttpUtil.addHeaderIfValueIsNotEmpty( headers, HttpUtil.X_FORWARDED_FOR_HEADER, - ObjectUtil.getIfNotNull(device, Device::getIpv6) - ); + ObjectUtil.getIfNotNull(device, Device::getIpv6)); HttpUtil.addHeaderIfValueIsNotEmpty( headers, HttpUtil.X_FORWARDED_FOR_HEADER, - ObjectUtil.getIfNotNull(device, Device::getIp) - ); + ObjectUtil.getIfNotNull(device, Device::getIp)); return headers; } @@ -112,11 +110,7 @@ private static MultiMap makeHeaders(BidRequest bidRequest) { public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - final boolean hasNoSeatBids = Optional.ofNullable(bidResponse.getSeatbid()).map(List::isEmpty).orElse(true); - return hasNoSeatBids - ? Result.withError(BidderError.badServerResponse("Empty SeatBid array")) - : Result.withValues(extractBids(bidResponse)); - + return Result.withValues(extractBids(bidResponse)); } catch (DecodeException e) { return Result.withError(BidderError.badServerResponse("Bad Server Response")); } catch (PreBidException e) { @@ -125,6 +119,10 @@ public Result> makeBids(BidderCall httpCall, BidRequ } private static List extractBids(BidResponse bidResponse) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + throw new PreBidException("Empty SeatBid array"); + } + return bidResponse.getSeatbid() .stream() .flatMap(seatBid -> Optional.ofNullable(seatBid.getBid()).orElse(List.of()).stream()) @@ -143,8 +141,7 @@ private static BidType getBidMediaType(Bid bid) { case 2 -> BidType.video; case 4 -> BidType.xNative; default -> throw new PreBidException( - "Unable to fetch mediaType " + bid.getMtype() + " in multi-format: " + bid.getImpid() - ); + "Unable to fetch mediaType " + bid.getMtype() + " in multi-format: " + bid.getImpid()); }; } diff --git a/src/test/java/org/prebid/server/bidder/gothamads/GothamAdsBidderTest.java b/src/test/java/org/prebid/server/bidder/gothamads/GothamAdsBidderTest.java index 12b9e46e638..e4453a1dfb8 100644 --- a/src/test/java/org/prebid/server/bidder/gothamads/GothamAdsBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/gothamads/GothamAdsBidderTest.java @@ -26,9 +26,9 @@ 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.function.Function.identity; +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.prebid.server.bidder.model.BidderError.Type; @@ -177,14 +177,14 @@ public void makeBidsShouldReturnErrorWhenImpTypeIsNotSupported() throws JsonProc .containsExactly(badServerResponse("Unable to fetch mediaType 3 in multi-format: 3")); } - private static BidRequest givenBidRequest(Function... impCustomizers) { + private static BidRequest givenBidRequest(UnaryOperator... impCustomizers) { return BidRequest.builder() .device(Device.builder().ua("ua").ip("ip").ipv6("ipv6").build()) .imp(Arrays.stream(impCustomizers).map(GothamAdsBidderTest::givenImp).toList()) .build(); } - private static Imp givenImp(Function impCustomizer) { + private static Imp givenImp(UnaryOperator impCustomizer) { return impCustomizer.apply(Imp.builder().id("imp_id").ext(givenImpExt())).build(); } 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 8f58a5f12e5..38986ccad05 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -177,6 +177,8 @@ adapters.gamoshi.enabled=true adapters.gamoshi.endpoint=http://localhost:8090/gamoshi-exchange adapters.globalsun.enabled=true adapters.globalsun.endpoint=http://localhost:8090/globalsun-exchange +adapters.gothamads.enabled=true +adapters.gothamads.endpoint=http://localhost:8090/gothamads-exchange adapters.grid.enabled=true adapters.grid.endpoint=http://localhost:8090/grid-exchange adapters.gumgum.enabled=true @@ -397,8 +399,6 @@ adapters.aax.enabled=true adapters.aax.endpoint=http://localhost:8090/aax-exchange adapters.zeta_global_ssp.enabled=true adapters.zeta_global_ssp.endpoint=http://localhost:8090/zeta_global_ssp-exchange -adapters.gothamads.enabled=true -adapters.gothamads.endpoint=http://localhost:8090/gothamads-exchange http-client.circuit-breaker.enabled=true http-client.circuit-breaker.idle-expire-hours=24 http-client.circuit-breaker.opening-threshold=1000