From 8ea1419f54c20b7efbd93923ea7b41c068abb585 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 17 Jan 2025 14:35:16 +0100 Subject: [PATCH 01/13] Kobler: New adapter (#3667) --- .../server/bidder/kobler/KoblerBidder.java | 154 ++++++++++++++++ .../ext/request/kobler/ExtImpKobler.java | 9 + .../config/bidder/KoblerConfiguration.java | 41 +++++ src/main/resources/bidder-config/kobler.yaml | 14 ++ .../static/bidder-params/kobler.json | 13 ++ .../bidder/kobler/KoblerBidderTest.java | 169 ++++++++++++++++++ .../org/prebid/server/it/KiviAdsTest.java | 8 +- .../java/org/prebid/server/it/KoblerTest.java | 31 ++++ .../kobler/test-auction-kobler-request.json | 28 +++ .../kobler/test-auction-kobler-response.json | 19 ++ .../kobler/test-kobler-bid-request.json | 28 +++ .../kobler/test-kobler-bid-response.json | 19 ++ .../server/it/test-application.properties | 2 + 13 files changed, 531 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/kobler/ExtImpKobler.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java create mode 100644 src/main/resources/bidder-config/kobler.yaml create mode 100644 src/main/resources/static/bidder-params/kobler.json create mode 100644 src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java create mode 100644 src/test/java/org/prebid/server/it/KoblerTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java new file mode 100644 index 00000000000..cecf1c40c30 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -0,0 +1,154 @@ +package org.prebid.server.bidder.kobler; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +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.Price; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.currency.CurrencyConversionService; +import org.prebid.server.exception.PreBidException; +import org.prebid.server.json.DecodeException; +import org.prebid.server.json.JacksonMapper; +import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.kobler.ExtImpKobler; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; +import org.prebid.server.util.HttpUtil; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class KoblerBidder implements Bidder { + + private static final TypeReference> KOBLER_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; + private static final String DEFAULT_BID_CURRENCY = "USD"; + + private final String endpointUrl; + private final CurrencyConversionService currencyConversionService; + private final JacksonMapper mapper; + + public KoblerBidder(String endpointUrl, + CurrencyConversionService currencyConversionService, + JacksonMapper mapper) { + + this.endpointUrl = HttpUtil.validateUrl(endpointUrl); + this.currencyConversionService = Objects.requireNonNull(currencyConversionService); + this.mapper = Objects.requireNonNull(mapper); + } + + + @Override + public Result>> makeHttpRequests(BidRequest bidRequest) { + final List errors = new ArrayList<>(); + final List> requests = new ArrayList<>(); + + for (Imp imp : bidRequest.getImp()) { + try { + final ExtImpKobler impExt = parseImpExt(imp); + final Imp modifiedImp = modifyImp(imp, impExt, bidRequest); + requests.add(makeHttpRequest(bidRequest, modifiedImp)); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + + return Result.of(requests, errors); + } + + private ExtImpKobler parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), KOBLER_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage()); + } + } + + private Imp modifyImp(Imp imp, ExtImpKobler extImpKobler, BidRequest bidRequest) { + final Price resolvedBidFloor = resolveBidFloor(imp, bidRequest); + + return imp.toBuilder() + .bidfloor(resolvedBidFloor.getValue()) + .bidfloorcur(resolvedBidFloor.getCurrency()) + .ext(mapper.mapper().valueToTree(extImpKobler)) + .build(); + } + + private Price resolveBidFloor(Imp imp, BidRequest bidRequest) { + final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); + return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, DEFAULT_BID_CURRENCY) + ? convertBidFloor(initialBidFloorPrice, bidRequest) + : initialBidFloorPrice; + } + + private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) { + final BigDecimal convertedPrice = currencyConversionService.convertCurrency( + bidFloorPrice.getValue(), + bidRequest, + bidFloorPrice.getCurrency(), + DEFAULT_BID_CURRENCY); + + return Price.of(DEFAULT_BID_CURRENCY, convertedPrice); + } + + private HttpRequest makeHttpRequest(BidRequest bidRequest, Imp imp) { + final BidRequest modifiedBidRequest = bidRequest.toBuilder() + .imp(Collections.singletonList(imp)) + .build(); + return BidderUtil.defaultRequest(modifiedBidRequest, endpointUrl, mapper); + } + + @Override + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final List errors = new ArrayList<>(); + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + return Result.of(extractBids(bidResponse, errors), errors); + } catch (DecodeException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private List extractBids(BidResponse bidResponse, List errors) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + return Collections.emptyList(); + } + return bidsFromResponse(bidResponse, errors); + } + + private List bidsFromResponse(BidResponse bidResponse, List errors) { + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .map(bid -> BidderBid.of(bid, getBidType(bid), bidResponse.getCur())) + .filter(Objects::nonNull) + .toList(); + } + + private BidType getBidType(Bid bid) { + final Integer markupType = ObjectUtils.defaultIfNull(bid.getMtype(), 0); + + return switch (markupType) { + case 1 -> BidType.banner; + default -> throw new PreBidException( + "could not define media type for impression: " + bid.getImpid()); + }; + } +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/kobler/ExtImpKobler.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/kobler/ExtImpKobler.java new file mode 100644 index 00000000000..ba8e94bb6a9 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/kobler/ExtImpKobler.java @@ -0,0 +1,9 @@ +package org.prebid.server.proto.openrtb.ext.request.kobler; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpKobler { + + Boolean test; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java new file mode 100644 index 00000000000..400e8f8d42a --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java @@ -0,0 +1,41 @@ +package org.prebid.server.spring.config.bidder; + +import jakarta.validation.constraints.NotBlank; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.kobler.KoblerBidder; +import org.prebid.server.currency.CurrencyConversionService; +import org.prebid.server.json.JacksonMapper; +import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties; +import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; +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; + +@Configuration +@PropertySource(value = "classpath:/bidder-config/kobler.yaml", factory = YamlPropertySourceFactory.class) +public class KoblerConfiguration { + + private static final String BIDDER_NAME = "kobler"; + + @Bean("koblerConfigurationProperties") + @ConfigurationProperties("adapters.kobler") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + BidderDeps koblerBidderDeps(BidderConfigurationProperties koblerConfigurationProperies, + CurrencyConversionService currencyConversionService, + @NotBlank @Value("#{external-url}") String externalUrl, + JacksonMapper mapper) { + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(koblerConfigurationProperies) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new KoblerBidder(config.getEndpoint(),currencyConversionService, mapper)) + .assemble(); + } +} diff --git a/src/main/resources/bidder-config/kobler.yaml b/src/main/resources/bidder-config/kobler.yaml new file mode 100644 index 00000000000..93844635f0d --- /dev/null +++ b/src/main/resources/bidder-config/kobler.yaml @@ -0,0 +1,14 @@ +adapters: + kobler: + endpoint: "https://bid.essrtb.com/bid/prebid_server_rtb_call" + endpointCompression: gzip + maintainer: + email: bidding-support@kobler.no + geoscope: + - NOR + - SWE + - DNK + capabilities: + site: + mediaTypes: + - banner diff --git a/src/main/resources/static/bidder-params/kobler.json b/src/main/resources/static/bidder-params/kobler.json new file mode 100644 index 00000000000..7e85601bfe8 --- /dev/null +++ b/src/main/resources/static/bidder-params/kobler.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Kobler Adapter Params", + "description": "A schema which validates params accepted by the Kobler adapter", + "type": "object", + + "properties": { + "test": { + "type": "boolean", + "description": "Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one." + } + } +} diff --git a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java new file mode 100644 index 00000000000..378aeac08a6 --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -0,0 +1,169 @@ +package org.prebid.server.bidder.kobler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderCall; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.HttpResponse; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.currency.CurrencyConversionService; +import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.kobler.ExtImpKobler; +import org.prebid.server.proto.openrtb.ext.response.BidType; + +import java.math.BigDecimal; +import java.util.List; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.BDDAssertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +public class KoblerBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://test.com"; + + @Mock + private CurrencyConversionService currencyConversionService; + + private KoblerBidder target; + + @BeforeEach + public void setUp() { + target = new KoblerBidder(ENDPOINT_URL, currencyConversionService, jacksonMapper); + } + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException().isThrownBy(() -> new KoblerBidder( + "invalid_url", currencyConversionService, jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldConvertCurrencyIfRequired() { + // given + given(currencyConversionService.convertCurrency(any(), any(), eq("EUR"), eq("USD"))) + .willReturn(BigDecimal.TEN); + + final BidRequest bidRequest = BidRequest.builder() + .imp(List.of(givenImp(imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")))) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsExactly(tuple(BigDecimal.TEN, "USD")); + } + + @Test + public void makeHttpRequestsShouldSetBidFloorIfConversionNotRequired() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(List.of(givenImp(imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("USD")))) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsExactly(tuple(BigDecimal.ONE, "USD")); + } + + @Test + public void makeBidsShouldReturnErrorForInvalidResponse() { + // given + final BidderCall httpCall = givenHttpCall(null, "invalid_response"); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> assertThat(error.getMessage()).contains("Failed to decode")); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListForEmptyBidResponse() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(BidResponse.builder().build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { + // given + final BidResponse bidResponse = givenBidResponse(bid -> bid.impid("impId").price(BigDecimal.ONE).mtype(1)); + final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(bidResponse)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(bidResponse.getSeatbid().get(0).getBid().get(0), BidType.banner, "USD")); + } + + private static Imp givenImp(UnaryOperator impCustomizer) { + return impCustomizer.apply( + Imp.builder().id("123").ext(givenImpExt(true))).build(); + } + + private static ObjectNode givenImpExt(boolean test) { + return mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(test))); + } + + private static BidResponse givenBidResponse(Function bidCustomizer) { + return BidResponse.builder() + .cur("USD") + .seatbid(List.of(SeatBid.builder() + .bid(List.of(bidCustomizer.apply(Bid.builder()).build())) + .build())) + .build(); + } + + private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { + return BidderCall.succeededHttp( + HttpRequest.builder().payload(bidRequest).build(), + HttpResponse.of(200, null, body), + null); + } +} + + diff --git a/src/test/java/org/prebid/server/it/KiviAdsTest.java b/src/test/java/org/prebid/server/it/KiviAdsTest.java index 2ee3d792929..b09cbf2d7df 100644 --- a/src/test/java/org/prebid/server/it/KiviAdsTest.java +++ b/src/test/java/org/prebid/server/it/KiviAdsTest.java @@ -19,15 +19,15 @@ public class KiviAdsTest extends IntegrationTest { public void openrtb2AuctionShouldRespondWithBidsFromKiviAds() throws IOException, JSONException { // given WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/kiviads-exchange")) - .withRequestBody(equalToJson(jsonFrom("openrtb2/kiviads/test-kiviads-bid-request.json"))) - .willReturn(aResponse().withBody(jsonFrom("openrtb2/kiviads/test-kiviads-bid-response.json")))); + .withRequestBody(equalToJson(jsonFrom("openrtb2/kiviads/test-kobler-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/kiviads/test-kobler-bid-response.json")))); // when - final Response response = responseFor("openrtb2/kiviads/test-auction-kiviads-request.json", + final Response response = responseFor("openrtb2/kiviads/test-auction-kobler-request.json", Endpoint.openrtb2_auction); // then - assertJsonEquals("openrtb2/kiviads/test-auction-kiviads-response.json", response, + assertJsonEquals("openrtb2/kiviads/test-auction-kobler-response.json", response, singletonList("kiviads")); } } diff --git a/src/test/java/org/prebid/server/it/KoblerTest.java b/src/test/java/org/prebid/server/it/KoblerTest.java new file mode 100644 index 00000000000..7df286f5d77 --- /dev/null +++ b/src/test/java/org/prebid/server/it/KoblerTest.java @@ -0,0 +1,31 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.prebid.server.model.Endpoint; + +import java.io.IOException; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static java.util.Collections.singletonList; + +public class KoblerTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromTheKoblerBidder() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/kobler-exchange")) + .withRequestBody(equalToJson( + jsonFrom("openrtb2/kobler/test-kobler-bid-request.json"))) + .willReturn(aResponse().withBody( + jsonFrom("openrtb2/kobler/test-kobler-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/kobler/test-auction-kobler-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/kobler/test-auction-kobler-response.json", response, singletonList("kobler")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-request.json b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-request.json new file mode 100644 index 00000000000..50b8e11ca82 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-request.json @@ -0,0 +1,28 @@ +{ + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { "w": 300, "h": 250 }, + { "w": 728, "h": 90 } + ] + }, + "bidfloor": 0.6, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "test": true + } + } + } + ], + "site": { + "id": "site-1", + "page": "http://test-page.com" + }, + "cur": ["USD"], + "test": 1, + "tmax": 500 +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-response.json b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-response.json new file mode 100644 index 00000000000..8b19bcd079d --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-response.json @@ -0,0 +1,19 @@ +{ + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "bid-1", + "impid": "imp-1", + "price": 1.0, + "adm": "
Test Ad
", + "crid": "creative-1", + "w": 300, + "h": 250 + } + ] + } + ], + "cur": "USD" +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json new file mode 100644 index 00000000000..50b8e11ca82 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json @@ -0,0 +1,28 @@ +{ + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { "w": 300, "h": 250 }, + { "w": 728, "h": 90 } + ] + }, + "bidfloor": 0.6, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "test": true + } + } + } + ], + "site": { + "id": "site-1", + "page": "http://test-page.com" + }, + "cur": ["USD"], + "test": 1, + "tmax": 500 +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-response.json new file mode 100644 index 00000000000..82ab7f669b2 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-response.json @@ -0,0 +1,19 @@ +{ + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "bid-1", + "impid": "imp-1", + "price": 1.0, + "adm": "
Test Ad from Kobler
", + "crid": "creative-1", + "w": 300, + "h": 250 + } + ] + } + ], + "cur": "USD" +} 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 2e115d00348..e705343676e 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -251,6 +251,8 @@ adapters.colossus.enabled=true adapters.colossus.endpoint=http://localhost:8090/colossus-exchange adapters.colossus.aliases.colossusssp.enabled=true adapters.colossus.aliases.colossusssp.endpoint=http://localhost:8090/colossusssp-exchange +adapters.kobler.enabled=true +adapters.kobler.endpoint=http://localhost:8090/kobler-exchange adapters.krushmedia.enabled=true adapters.krushmedia.endpoint=http://localhost:8090/krushmedia-exchange adapters.lemmadigital.enabled=true From 3e4f05e369a361330d245fc79afc67be66c30faa Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 17 Jan 2025 14:52:37 +0100 Subject: [PATCH 02/13] fix clean code --- .../org/prebid/server/bidder/kobler/KoblerBidder.java | 1 - .../server/spring/config/bidder/KoblerConfiguration.java | 8 +++++--- src/test/java/org/prebid/server/it/KoblerTest.java | 5 ++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java index cecf1c40c30..4b069730d9e 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -52,7 +52,6 @@ public KoblerBidder(String endpointUrl, this.mapper = Objects.requireNonNull(mapper); } - @Override public Result>> makeHttpRequests(BidRequest bidRequest) { final List errors = new ArrayList<>(); diff --git a/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java index 400e8f8d42a..65d737245c1 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java @@ -1,7 +1,5 @@ package org.prebid.server.spring.config.bidder; -import jakarta.validation.constraints.NotBlank; - import org.prebid.server.bidder.BidderDeps; import org.prebid.server.bidder.kobler.KoblerBidder; import org.prebid.server.currency.CurrencyConversionService; @@ -16,6 +14,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import jakarta.validation.constraints.NotBlank; + @Configuration @PropertySource(value = "classpath:/bidder-config/kobler.yaml", factory = YamlPropertySourceFactory.class) public class KoblerConfiguration { @@ -28,14 +28,16 @@ BidderConfigurationProperties configurationProperties() { return new BidderConfigurationProperties(); } + @Bean BidderDeps koblerBidderDeps(BidderConfigurationProperties koblerConfigurationProperies, CurrencyConversionService currencyConversionService, @NotBlank @Value("#{external-url}") String externalUrl, JacksonMapper mapper) { + return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(koblerConfigurationProperies) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new KoblerBidder(config.getEndpoint(),currencyConversionService, mapper)) + .bidderCreator(config -> new KoblerBidder(config.getEndpoint(), currencyConversionService, mapper)) .assemble(); } } diff --git a/src/test/java/org/prebid/server/it/KoblerTest.java b/src/test/java/org/prebid/server/it/KoblerTest.java index 7df286f5d77..ca5a3c74610 100644 --- a/src/test/java/org/prebid/server/it/KoblerTest.java +++ b/src/test/java/org/prebid/server/it/KoblerTest.java @@ -7,7 +7,10 @@ import java.io.IOException; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static java.util.Collections.singletonList; public class KoblerTest extends IntegrationTest { From c3e11ce4e769e0ce14a62941fa9496abee930ea7 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 17 Jan 2025 16:47:31 +0100 Subject: [PATCH 03/13] fix bugs --- .../spring/config/bidder/KoblerConfiguration.java | 10 +++++----- src/main/resources/bidder-config/kobler.yaml | 12 +++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java index 65d737245c1..a82e7115256 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java @@ -29,13 +29,13 @@ BidderConfigurationProperties configurationProperties() { } @Bean - BidderDeps koblerBidderDeps(BidderConfigurationProperties koblerConfigurationProperies, - CurrencyConversionService currencyConversionService, - @NotBlank @Value("#{external-url}") String externalUrl, - JacksonMapper mapper) { + BidderDeps koblerBidderDeps(BidderConfigurationProperties koblerConfigurationProperties, + CurrencyConversionService currencyConversionService, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) - .withConfig(koblerConfigurationProperies) + .withConfig(koblerConfigurationProperties) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) .bidderCreator(config -> new KoblerBidder(config.getEndpoint(), currencyConversionService, mapper)) .assemble(); diff --git a/src/main/resources/bidder-config/kobler.yaml b/src/main/resources/bidder-config/kobler.yaml index 93844635f0d..e44d86ca4e2 100644 --- a/src/main/resources/bidder-config/kobler.yaml +++ b/src/main/resources/bidder-config/kobler.yaml @@ -1,14 +1,12 @@ adapters: kobler: endpoint: "https://bid.essrtb.com/bid/prebid_server_rtb_call" - endpointCompression: gzip - maintainer: - email: bidding-support@kobler.no geoscope: - NOR - SWE - DNK - capabilities: - site: - mediaTypes: - - banner + meta-info: + maintainer-email: bidding-support@kobler.no + site-media-types: + - banner + vendor-id: 0 From 5ca450c218dec0b693c2b7585a87d01eccd78858 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Mon, 20 Jan 2025 15:23:50 +0100 Subject: [PATCH 04/13] fix bugs --- .../server/bidder/kobler/KoblerBidder.java | 17 +++++- .../java/org/prebid/server/it/KoblerTest.java | 1 - .../kobler/test-auction-kobler-request.json | 29 ++++------ .../kobler/test-auction-kobler-response.json | 39 ++++++++++--- .../kobler/test-kobler-bid-request.json | 57 +++++++++++++------ .../kobler/test-kobler-bid-response.json | 22 +++---- 6 files changed, 109 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java index 4b069730d9e..04fee5a448e 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -38,6 +38,7 @@ public class KoblerBidder implements Bidder { new TypeReference<>() { }; private static final String DEFAULT_BID_CURRENCY = "USD"; + private static final String DEV_ENDPOINT = "https://bid-service.dev.essrtb.com/bid/prebid_server_rtb_call"; private final String endpointUrl; private final CurrencyConversionService currencyConversionService; @@ -57,11 +58,18 @@ public Result>> makeHttpRequests(BidRequest bidRequ final List errors = new ArrayList<>(); final List> requests = new ArrayList<>(); + boolean testMode = false; + for (Imp imp : bidRequest.getImp()) { try { final ExtImpKobler impExt = parseImpExt(imp); + + if (bidRequest.getImp().indexOf(imp) == 0) { + testMode = impExt.getTest(); + } + final Imp modifiedImp = modifyImp(imp, impExt, bidRequest); - requests.add(makeHttpRequest(bidRequest, modifiedImp)); + requests.add(makeHttpRequest(bidRequest, modifiedImp, testMode)); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); } @@ -105,11 +113,14 @@ private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) { return Price.of(DEFAULT_BID_CURRENCY, convertedPrice); } - private HttpRequest makeHttpRequest(BidRequest bidRequest, Imp imp) { + private HttpRequest makeHttpRequest(BidRequest bidRequest, Imp imp, boolean testMode) { final BidRequest modifiedBidRequest = bidRequest.toBuilder() .imp(Collections.singletonList(imp)) .build(); - return BidderUtil.defaultRequest(modifiedBidRequest, endpointUrl, mapper); + + final String endpoint = testMode ? DEV_ENDPOINT : endpointUrl; + + return BidderUtil.defaultRequest(modifiedBidRequest, endpoint, mapper); } @Override diff --git a/src/test/java/org/prebid/server/it/KoblerTest.java b/src/test/java/org/prebid/server/it/KoblerTest.java index ca5a3c74610..55dea8fe4ad 100644 --- a/src/test/java/org/prebid/server/it/KoblerTest.java +++ b/src/test/java/org/prebid/server/it/KoblerTest.java @@ -27,7 +27,6 @@ public void openrtb2AuctionShouldRespondWithBidsFromTheKoblerBidder() throws IOE // when final Response response = responseFor("openrtb2/kobler/test-auction-kobler-request.json", Endpoint.openrtb2_auction); - // then assertJsonEquals("openrtb2/kobler/test-auction-kobler-response.json", response, singletonList("kobler")); } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-request.json b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-request.json index 50b8e11ca82..cbb25bca256 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-request.json @@ -1,28 +1,23 @@ { - "id": "test-request-id", + "id": "request_id", "imp": [ { - "id": "imp-1", + "id": "imp_id", "banner": { - "format": [ - { "w": 300, "h": 250 }, - { "w": 728, "h": 90 } - ] + "w": 300, + "h": 250 }, - "bidfloor": 0.6, - "bidfloorcur": "USD", "ext": { - "bidder": { - "test": true + "kobler": { + "test": false } } } ], - "site": { - "id": "site-1", - "page": "http://test-page.com" - }, - "cur": ["USD"], - "test": 1, - "tmax": 500 + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-response.json b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-response.json index 8b19bcd079d..7c9f11b5cf5 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-auction-kobler-response.json @@ -1,19 +1,40 @@ { - "id": "test-request-id", + "id": "request_id", "seatbid": [ { "bid": [ { - "id": "bid-1", - "impid": "imp-1", - "price": 1.0, - "adm": "
Test Ad
", - "crid": "creative-1", + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 3.33, + "adm": "adm001", + "adid": "adid001", + "cid": "cid001", + "crid": "crid001", "w": 300, - "h": 250 + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + }, + "origbidcpm": 3.33 + } } - ] + ], + "seat": "kobler", + "group": 0 } ], - "cur": "USD" + "cur": "USD", + "ext": { + "responsetimemillis": { + "kobler": "{{ kobler.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json index 50b8e11ca82..0598d51c5ec 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json @@ -1,28 +1,53 @@ { - "id": "test-request-id", + "id": "request_id", "imp": [ { - "id": "imp-1", + "id": "imp_id", + "secure": 1, "banner": { - "format": [ - { "w": 300, "h": 250 }, - { "w": 728, "h": 90 } - ] + "w": 300, + "h": 250 }, - "bidfloor": 0.6, - "bidfloorcur": "USD", "ext": { - "bidder": { - "test": true - } + "test": false } } ], + "source": { + "tid": "${json-unit.any-string}" + }, "site": { - "id": "site-1", - "page": "http://test-page.com" + "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 + } }, - "cur": ["USD"], - "test": 1, - "tmax": 500 + "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/kobler/test-kobler-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-response.json index 82ab7f669b2..2769168e6ed 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-response.json @@ -1,19 +1,21 @@ { - "id": "test-request-id", + "id": "request_id", "seatbid": [ { "bid": [ { - "id": "bid-1", - "impid": "imp-1", - "price": 1.0, - "adm": "
Test Ad from Kobler
", - "crid": "creative-1", - "w": 300, - "h": 250 + "id": "bid_id", + "impid": "imp_id", + "price": 3.33, + "adid": "adid001", + "crid": "crid001", + "cid": "cid001", + "adm": "adm001", + "mtype": 1, + "h": 250, + "w": 300 } ] } - ], - "cur": "USD" + ] } From 7293b44efa5b8233aaeba0921dfbc9a340c9c6b0 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Mon, 20 Jan 2025 15:39:00 +0100 Subject: [PATCH 05/13] fix bugs --- src/test/java/org/prebid/server/it/KiviAdsTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/prebid/server/it/KiviAdsTest.java b/src/test/java/org/prebid/server/it/KiviAdsTest.java index b09cbf2d7df..2ee3d792929 100644 --- a/src/test/java/org/prebid/server/it/KiviAdsTest.java +++ b/src/test/java/org/prebid/server/it/KiviAdsTest.java @@ -19,15 +19,15 @@ public class KiviAdsTest extends IntegrationTest { public void openrtb2AuctionShouldRespondWithBidsFromKiviAds() throws IOException, JSONException { // given WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/kiviads-exchange")) - .withRequestBody(equalToJson(jsonFrom("openrtb2/kiviads/test-kobler-bid-request.json"))) - .willReturn(aResponse().withBody(jsonFrom("openrtb2/kiviads/test-kobler-bid-response.json")))); + .withRequestBody(equalToJson(jsonFrom("openrtb2/kiviads/test-kiviads-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/kiviads/test-kiviads-bid-response.json")))); // when - final Response response = responseFor("openrtb2/kiviads/test-auction-kobler-request.json", + final Response response = responseFor("openrtb2/kiviads/test-auction-kiviads-request.json", Endpoint.openrtb2_auction); // then - assertJsonEquals("openrtb2/kiviads/test-auction-kobler-response.json", response, + assertJsonEquals("openrtb2/kiviads/test-auction-kiviads-response.json", response, singletonList("kiviads")); } } From ac1eb72afe9d3d8c05fe40d0fa0eda2a6a935ad3 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Thu, 30 Jan 2025 10:51:32 +0100 Subject: [PATCH 06/13] fix comments --- .../server/bidder/kobler/KoblerBidder.java | 150 ++++++++----- .../ext/request/kobler/ExtImpKobler.java | 2 + .../bidder/kobler/KoblerBidderTest.java | 206 +++++++++++------- .../kobler/test-kobler-bid-request.json | 13 +- 4 files changed, 237 insertions(+), 134 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java index 04fee5a448e..117e77b3849 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -1,28 +1,30 @@ package org.prebid.server.bidder.kobler; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; +import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.ObjectUtils; 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.Price; import org.prebid.server.bidder.model.Result; import org.prebid.server.currency.CurrencyConversionService; import org.prebid.server.exception.PreBidException; import org.prebid.server.json.DecodeException; +import org.prebid.server.json.EncodeException; import org.prebid.server.json.JacksonMapper; import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.kobler.ExtImpKobler; import org.prebid.server.proto.openrtb.ext.response.BidType; -import org.prebid.server.util.BidderUtil; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; import org.prebid.server.util.HttpUtil; import java.math.BigDecimal; @@ -37,6 +39,7 @@ public class KoblerBidder implements Bidder { private static final TypeReference> KOBLER_EXT_TYPE_REFERENCE = new TypeReference<>() { }; + private static final String EXT_PREBID = "prebid"; private static final String DEFAULT_BID_CURRENCY = "USD"; private static final String DEV_ENDPOINT = "https://bid-service.dev.essrtb.com/bid/prebid_server_rtb_call"; @@ -56,71 +59,90 @@ public KoblerBidder(String endpointUrl, @Override public Result>> makeHttpRequests(BidRequest bidRequest) { final List errors = new ArrayList<>(); - final List> requests = new ArrayList<>(); - boolean testMode = false; + final List modifiedImps = new ArrayList<>(); + + final List currencies = bidRequest.getCur() != null + ? new ArrayList<>(bidRequest.getCur()) + : new ArrayList<>(); + if (!currencies.contains(DEFAULT_BID_CURRENCY)) { + currencies.add(DEFAULT_BID_CURRENCY); + } - for (Imp imp : bidRequest.getImp()) { + BidRequest modifiedRequest = bidRequest.toBuilder().cur(currencies).build(); + + for (Imp imp : modifiedRequest.getImp()) { try { - final ExtImpKobler impExt = parseImpExt(imp); + final Imp processedImp = processImp(modifiedRequest, imp, errors); + modifiedImps.add(processedImp); - if (bidRequest.getImp().indexOf(imp) == 0) { - testMode = impExt.getTest(); + if (modifiedImps.size() == 1) { + testMode = extractTestMode(processedImp); } - - final Imp modifiedImp = modifyImp(imp, impExt, bidRequest); - requests.add(makeHttpRequest(bidRequest, modifiedImp, testMode)); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); } } - return Result.of(requests, errors); - } - - private ExtImpKobler parseImpExt(Imp imp) { - try { - return mapper.mapper().convertValue(imp.getExt(), KOBLER_EXT_TYPE_REFERENCE).getBidder(); - } catch (IllegalArgumentException e) { - throw new PreBidException(e.getMessage()); + if (modifiedImps.isEmpty()) { + errors.add(BidderError.badInput("No valid impressions")); + return Result.withErrors(errors); } - } - private Imp modifyImp(Imp imp, ExtImpKobler extImpKobler, BidRequest bidRequest) { - final Price resolvedBidFloor = resolveBidFloor(imp, bidRequest); + modifiedRequest = modifiedRequest.toBuilder().imp(modifiedImps).build(); - return imp.toBuilder() - .bidfloor(resolvedBidFloor.getValue()) - .bidfloorcur(resolvedBidFloor.getCurrency()) - .ext(mapper.mapper().valueToTree(extImpKobler)) - .build(); - } + final String endpoint = testMode ? DEV_ENDPOINT : endpointUrl; - private Price resolveBidFloor(Imp imp, BidRequest bidRequest) { - final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); - return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, DEFAULT_BID_CURRENCY) - ? convertBidFloor(initialBidFloorPrice, bidRequest) - : initialBidFloorPrice; + try { + return Result.of(Collections.singletonList( + HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpoint) + .headers(HttpUtil.headers()) + .body(mapper.encodeToBytes(modifiedRequest)) + .payload(modifiedRequest) + .build() + ), errors); + } catch (EncodeException e) { + errors.add(BidderError.badInput("Failed to encode request: " + e.getMessage())); + return Result.withErrors(errors); + } } - private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) { - final BigDecimal convertedPrice = currencyConversionService.convertCurrency( - bidFloorPrice.getValue(), - bidRequest, - bidFloorPrice.getCurrency(), - DEFAULT_BID_CURRENCY); - - return Price.of(DEFAULT_BID_CURRENCY, convertedPrice); + private Imp processImp(BidRequest bidRequest, Imp imp, List errors) { + if (imp.getBidfloor() != null + && imp.getBidfloor().compareTo(BigDecimal.ZERO) > 0 + && imp.getBidfloorcur() != null) { + final String bidFloorCur = imp.getBidfloorcur().toUpperCase(); + if (!DEFAULT_BID_CURRENCY.equals(bidFloorCur)) { + try { + final BigDecimal convertedPrice = currencyConversionService.convertCurrency( + imp.getBidfloor(), + bidRequest, + bidFloorCur, + DEFAULT_BID_CURRENCY + ); + return imp.toBuilder() + .bidfloor(convertedPrice) + .bidfloorcur(DEFAULT_BID_CURRENCY) + .build(); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + } + return imp; } - private HttpRequest makeHttpRequest(BidRequest bidRequest, Imp imp, boolean testMode) { - final BidRequest modifiedBidRequest = bidRequest.toBuilder() - .imp(Collections.singletonList(imp)) - .build(); - - final String endpoint = testMode ? DEV_ENDPOINT : endpointUrl; - - return BidderUtil.defaultRequest(modifiedBidRequest, endpoint, mapper); + public boolean extractTestMode(Imp imp) { + try { + final ExtPrebid extPrebid = mapper.mapper().convertValue(imp.getExt(), + KOBLER_EXT_TYPE_REFERENCE); + final ExtImpKobler extImpKobler = extPrebid != null ? extPrebid.getBidder() : null; + return extImpKobler != null && Boolean.TRUE.equals(extImpKobler.getTest()); + } catch (IllegalArgumentException e) { + return false; + } } @Override @@ -153,12 +175,28 @@ private List bidsFromResponse(BidResponse bidResponse, List BidType.banner; - default -> throw new PreBidException( - "could not define media type for impression: " + bid.getImpid()); - }; + final ObjectNode prebidNode = (ObjectNode) bid.getExt().get(EXT_PREBID); + if (prebidNode == null) { + return BidType.banner; + } + + final ExtBidPrebid extBidPrebid = parseExtBidPrebid(prebidNode); + if (extBidPrebid == null || extBidPrebid.getType() == null) { + return BidType.banner; + } + + return extBidPrebid.getType(); // jeśli udało się sparsować + } + + private ExtBidPrebid parseExtBidPrebid(ObjectNode prebid) { + try { + return mapper.mapper().treeToValue(prebid, ExtBidPrebid.class); + } catch (JsonProcessingException e) { + return null; + } } } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/kobler/ExtImpKobler.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/kobler/ExtImpKobler.java index ba8e94bb6a9..dead820b18b 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/kobler/ExtImpKobler.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/kobler/ExtImpKobler.java @@ -1,9 +1,11 @@ package org.prebid.server.proto.openrtb.ext.request.kobler; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Value; @Value(staticConstructor = "of") public class ExtImpKobler { + @JsonProperty("test") Boolean test; } diff --git a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java index 378aeac08a6..54631266217 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -15,30 +15,34 @@ import org.prebid.server.VertxTest; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; +import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.HttpRequest; import org.prebid.server.bidder.model.HttpResponse; import org.prebid.server.bidder.model.Result; import org.prebid.server.currency.CurrencyConversionService; +import org.prebid.server.exception.PreBidException; import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.kobler.ExtImpKobler; import org.prebid.server.proto.openrtb.ext.response.BidType; import java.math.BigDecimal; import java.util.List; -import java.util.function.Function; import java.util.function.UnaryOperator; +import static java.util.Collections.emptyList; +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.BDDAssertions.tuple; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class KoblerBidderTest extends VertxTest { private static final String ENDPOINT_URL = "https://test.com"; + private static final String DEV_ENDPOINT = "https://bid-service.dev.essrtb.com/bid/prebid_server_rtb_call"; @Mock private CurrencyConversionService currencyConversionService; @@ -57,113 +61,169 @@ public void creationShouldFailOnInvalidEndpointUrl() { } @Test - public void makeHttpRequestsShouldConvertCurrencyIfRequired() { - // given - given(currencyConversionService.convertCurrency(any(), any(), eq("EUR"), eq("USD"))) - .willReturn(BigDecimal.TEN); - - final BidRequest bidRequest = BidRequest.builder() - .imp(List.of(givenImp(imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")))) + public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { + // Given + BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .bidfloor(BigDecimal.ONE) + .bidfloorcur("EUR") + .ext(mapper.createObjectNode()) + .build())) .build(); - // when - final Result>> result = target.makeHttpRequests(bidRequest); + when(currencyConversionService.convertCurrency(any(), any(), any(), any())) + .thenThrow(new PreBidException("Currency conversion failed")); - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getBidfloor, Imp::getBidfloorcur) - .containsExactly(tuple(BigDecimal.TEN, "USD")); + // When + Result>> result = target.makeHttpRequests(bidRequest); + + // Then + assertThat(result.getErrors()).hasSize(1) + .satisfies(errors -> assertThat(errors.get(0).getMessage()).contains("Currency conversion failed")); } @Test - public void makeHttpRequestsShouldSetBidFloorIfConversionNotRequired() { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(List.of(givenImp(imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("USD")))) - .build(); + public void makeHttpRequestsShouldReturnErrorIfNoImps() { + // Given + BidRequest bidRequest = BidRequest.builder().imp(emptyList()).build(); - // when - final Result>> result = target.makeHttpRequests(bidRequest); + // When + Result>> result = target.makeHttpRequests(bidRequest); - // then + // Then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> assertThat(error.getMessage()).contains("No valid impressions")); + } + + @Test + public void makeHttpRequestsShouldConvertBidFloorCurrency() { + // Given + BidRequest bidRequest = givenBidRequest(imp -> imp + .bidfloor(BigDecimal.ONE) + .bidfloorcur("EUR")); + + when(currencyConversionService.convertCurrency(any(), any(), any(), any())) + .thenReturn(BigDecimal.TEN); + + // When + Result>> result = target.makeHttpRequests(bidRequest); + + // Then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) + assertThat(result.getValue().get(0).getPayload().getImp()) .extracting(Imp::getBidfloor, Imp::getBidfloorcur) - .containsExactly(tuple(BigDecimal.ONE, "USD")); + .containsExactly(tuple(BigDecimal.TEN, "USD")); } @Test - public void makeBidsShouldReturnErrorForInvalidResponse() { - // given - final BidderCall httpCall = givenHttpCall(null, "invalid_response"); + public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { + // Given + BidRequest bidRequest = givenBidRequest(imp -> imp + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true))))); - // when - final Result> result = target.makeBids(httpCall, null); + // When + Result>> result = target.makeHttpRequests(bidRequest); - // then - assertThat(result.getErrors()).hasSize(1) - .allSatisfy(error -> assertThat(error.getMessage()).contains("Failed to decode")); - assertThat(result.getValue()).isEmpty(); + // Then + assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue().get(0).getUri()).isEqualTo(DEV_ENDPOINT); } @Test - public void makeBidsShouldReturnEmptyListForEmptyBidResponse() throws JsonProcessingException { - // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(BidResponse.builder().build())); + public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { + // Given + BidRequest bidRequest = givenBidRequest(identity()) + .toBuilder().cur(singletonList("EUR")).build(); - // when - final Result> result = target.makeBids(httpCall, null); + // When + Result>> result = target.makeHttpRequests(bidRequest); - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); + // Then + assertThat(result.getValue().get(0).getPayload().getCur()) + .containsExactlyInAnyOrder("EUR", "USD"); } @Test - public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { - // given - final BidResponse bidResponse = givenBidResponse(bid -> bid.impid("impId").price(BigDecimal.ONE).mtype(1)); - final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(bidResponse)); + public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { + // Given + BidderCall httpCall = givenHttpCall(); + + // When + Result> result = target.makeBids(httpCall, BidRequest.builder().build()); - // when - final Result> result = target.makeBids(httpCall, null); + // Then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(error.getMessage()).startsWith("Failed to decode"); + }); + } - // then + @Test + public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingException { + // Given + ObjectNode bidExt = mapper.createObjectNode() + .set("prebid", mapper.createObjectNode().put("type", "banner")); + + BidderCall httpCall = givenHttpCall( + BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(Bid.builder() + .impid("123") + .ext(bidExt) + .build())) + .build())) + .build()); + + // When + Result> result = target.makeBids(httpCall, BidRequest.builder().build()); + + // Then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .containsExactly(BidderBid.of(bidResponse.getSeatbid().get(0).getBid().get(0), BidType.banner, "USD")); + .extracting(BidderBid::getType) + .containsExactly(BidType.banner); } - private static Imp givenImp(UnaryOperator impCustomizer) { - return impCustomizer.apply( - Imp.builder().id("123").ext(givenImpExt(true))).build(); + @Test + public void extractTestModeShouldReturnTrueWhenImpExtHasTestTrue() { + // Given + Imp imp = Imp.builder() + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true)))) + .build(); + + // When + boolean testMode = target.extractTestMode(imp); + + // Then + assertThat(testMode).isTrue(); } - private static ObjectNode givenImpExt(boolean test) { - return mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(test))); + private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { + return givenBidRequest(identity(), impCustomizer); } - private static BidResponse givenBidResponse(Function bidCustomizer) { - return BidResponse.builder() - .cur("USD") - .seatbid(List.of(SeatBid.builder() - .bid(List.of(bidCustomizer.apply(Bid.builder()).build())) - .build())) + private static BidRequest givenBidRequest( + UnaryOperator bidRequestCustomizer, + UnaryOperator impCustomizer) { + + return bidRequestCustomizer.apply(BidRequest.builder() + .imp(singletonList(impCustomizer.apply(Imp.builder().id("123")).build()))) .build(); } - private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { + private BidderCall givenHttpCall(BidResponse bidResponse) throws JsonProcessingException { return BidderCall.succeededHttp( - HttpRequest.builder().payload(bidRequest).build(), - HttpResponse.of(200, null, body), + HttpRequest.builder().payload(null).build(), + HttpResponse.of(200, null, mapper.writeValueAsString(bidResponse)), null); } -} - + private BidderCall givenHttpCall() { + return BidderCall.succeededHttp( + HttpRequest.builder().payload(null).build(), + HttpResponse.of(200, null, "invalid"), + null); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json index 0598d51c5ec..83770158d3d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/kobler/test-kobler-bid-request.json @@ -3,19 +3,19 @@ "imp": [ { "id": "imp_id", - "secure": 1, "banner": { "w": 300, "h": 250 }, + "secure": 1, "ext": { - "test": false + "tid": "${json-unit.any-string}", + "bidder": { + "test": false + } } } ], - "source": { - "tid": "${json-unit.any-string}" - }, "site": { "domain": "www.example.com", "page": "http://www.example.com", @@ -35,6 +35,9 @@ "cur": [ "USD" ], + "source": { + "tid": "${json-unit.any-string}" + }, "regs": { "ext": { "gdpr": 0 From 9287f80246e404c95bd1124e22173302026be535 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Thu, 30 Jan 2025 12:03:27 +0100 Subject: [PATCH 07/13] fix checkstyle --- .../bidder/kobler/KoblerBidderTest.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java index 54631266217..f9aa7f3e9e6 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -63,7 +63,7 @@ public void creationShouldFailOnInvalidEndpointUrl() { @Test public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { // Given - BidRequest bidRequest = BidRequest.builder() + final BidRequest bidRequest = BidRequest.builder() .imp(singletonList(Imp.builder() .bidfloor(BigDecimal.ONE) .bidfloorcur("EUR") @@ -75,7 +75,7 @@ public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { .thenThrow(new PreBidException("Currency conversion failed")); // When - Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // Then assertThat(result.getErrors()).hasSize(1) @@ -85,10 +85,10 @@ public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { @Test public void makeHttpRequestsShouldReturnErrorIfNoImps() { // Given - BidRequest bidRequest = BidRequest.builder().imp(emptyList()).build(); + final BidRequest bidRequest = BidRequest.builder().imp(emptyList()).build(); // When - Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // Then assertThat(result.getErrors()).hasSize(1) @@ -98,7 +98,7 @@ public void makeHttpRequestsShouldReturnErrorIfNoImps() { @Test public void makeHttpRequestsShouldConvertBidFloorCurrency() { // Given - BidRequest bidRequest = givenBidRequest(imp -> imp + final BidRequest bidRequest = givenBidRequest(imp -> imp .bidfloor(BigDecimal.ONE) .bidfloorcur("EUR")); @@ -106,7 +106,7 @@ public void makeHttpRequestsShouldConvertBidFloorCurrency() { .thenReturn(BigDecimal.TEN); // When - Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // Then assertThat(result.getErrors()).isEmpty(); @@ -118,11 +118,11 @@ public void makeHttpRequestsShouldConvertBidFloorCurrency() { @Test public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { // Given - BidRequest bidRequest = givenBidRequest(imp -> imp + final BidRequest bidRequest = givenBidRequest(imp -> imp .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true))))); // When - Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // Then assertThat(result.getValue()).hasSize(1); @@ -132,11 +132,11 @@ public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { @Test public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { // Given - BidRequest bidRequest = givenBidRequest(identity()) + final BidRequest bidRequest = givenBidRequest(identity()) .toBuilder().cur(singletonList("EUR")).build(); // When - Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // Then assertThat(result.getValue().get(0).getPayload().getCur()) @@ -146,10 +146,10 @@ public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { @Test public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { // Given - BidderCall httpCall = givenHttpCall(); + final BidderCall httpCall = givenHttpCall(); // When - Result> result = target.makeBids(httpCall, BidRequest.builder().build()); + final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); // Then assertThat(result.getErrors()).hasSize(1) @@ -162,10 +162,10 @@ public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { @Test public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingException { // Given - ObjectNode bidExt = mapper.createObjectNode() + final ObjectNode bidExt = mapper.createObjectNode() .set("prebid", mapper.createObjectNode().put("type", "banner")); - BidderCall httpCall = givenHttpCall( + final BidderCall httpCall = givenHttpCall( BidResponse.builder() .cur("USD") .seatbid(singletonList(SeatBid.builder() @@ -177,7 +177,7 @@ public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingExce .build()); // When - Result> result = target.makeBids(httpCall, BidRequest.builder().build()); + final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); // Then assertThat(result.getErrors()).isEmpty(); @@ -189,12 +189,12 @@ public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingExce @Test public void extractTestModeShouldReturnTrueWhenImpExtHasTestTrue() { // Given - Imp imp = Imp.builder() + final Imp imp = Imp.builder() .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true)))) .build(); // When - boolean testMode = target.extractTestMode(imp); + final boolean testMode = target.extractTestMode(imp); // Then assertThat(testMode).isTrue(); From 857a3082e07a0fecc0edcf79228b1edafa5ca9e5 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Thu, 30 Jan 2025 13:16:11 +0100 Subject: [PATCH 08/13] fix yaml --- src/main/resources/bidder-config/kobler.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/bidder-config/kobler.yaml b/src/main/resources/bidder-config/kobler.yaml index e44d86ca4e2..d01e7aa7318 100644 --- a/src/main/resources/bidder-config/kobler.yaml +++ b/src/main/resources/bidder-config/kobler.yaml @@ -1,6 +1,7 @@ adapters: kobler: endpoint: "https://bid.essrtb.com/bid/prebid_server_rtb_call" + endpoint-compression: gzip geoscope: - NOR - SWE From c385084b887281944edc6a9e021f418ff3d3d9b8 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 31 Jan 2025 14:47:52 +0100 Subject: [PATCH 09/13] fix comments --- .../server/bidder/kobler/KoblerBidder.java | 92 +++++++++---------- .../bidder/kobler/KoblerBidderTest.java | 46 +++++++--- 2 files changed, 78 insertions(+), 60 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java index 117e77b3849..efe57550397 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -8,13 +8,13 @@ import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; -import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; import org.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.Price; import org.prebid.server.bidder.model.Result; import org.prebid.server.currency.CurrencyConversionService; import org.prebid.server.exception.PreBidException; @@ -25,6 +25,7 @@ import org.prebid.server.proto.openrtb.ext.request.kobler.ExtImpKobler; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; +import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; import java.math.BigDecimal; @@ -69,16 +70,19 @@ public Result>> makeHttpRequests(BidRequest bidRequ currencies.add(DEFAULT_BID_CURRENCY); } - BidRequest modifiedRequest = bidRequest.toBuilder().cur(currencies).build(); + final List imps = bidRequest.getImp(); + if (!imps.isEmpty()) { + try { + testMode = parseImpExt(imps.get(0)).getTest(); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } - for (Imp imp : modifiedRequest.getImp()) { + for (Imp imp : imps) { try { - final Imp processedImp = processImp(modifiedRequest, imp, errors); + final Imp processedImp = processImp(bidRequest, imp); modifiedImps.add(processedImp); - - if (modifiedImps.size() == 1) { - testMode = extractTestMode(processedImp); - } } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); } @@ -89,59 +93,53 @@ public Result>> makeHttpRequests(BidRequest bidRequ return Result.withErrors(errors); } - modifiedRequest = modifiedRequest.toBuilder().imp(modifiedImps).build(); + final BidRequest modifiedRequest = bidRequest.toBuilder() + .cur(currencies) + .imp(modifiedImps) + .build(); final String endpoint = testMode ? DEV_ENDPOINT : endpointUrl; try { - return Result.of(Collections.singletonList( - HttpRequest.builder() - .method(HttpMethod.POST) - .uri(endpoint) - .headers(HttpUtil.headers()) - .body(mapper.encodeToBytes(modifiedRequest)) - .payload(modifiedRequest) - .build() - ), errors); + final HttpRequest httpRequest = BidderUtil.defaultRequest(modifiedRequest, endpoint, mapper); + return Result.of(Collections.singletonList(httpRequest), errors); } catch (EncodeException e) { errors.add(BidderError.badInput("Failed to encode request: " + e.getMessage())); return Result.withErrors(errors); } } - private Imp processImp(BidRequest bidRequest, Imp imp, List errors) { - if (imp.getBidfloor() != null - && imp.getBidfloor().compareTo(BigDecimal.ZERO) > 0 - && imp.getBidfloorcur() != null) { - final String bidFloorCur = imp.getBidfloorcur().toUpperCase(); - if (!DEFAULT_BID_CURRENCY.equals(bidFloorCur)) { - try { - final BigDecimal convertedPrice = currencyConversionService.convertCurrency( - imp.getBidfloor(), - bidRequest, - bidFloorCur, - DEFAULT_BID_CURRENCY - ); - return imp.toBuilder() - .bidfloor(convertedPrice) - .bidfloorcur(DEFAULT_BID_CURRENCY) - .build(); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); - } - } - } - return imp; + private Imp processImp(BidRequest bidRequest, Imp imp) { + final Price resolvedBidFloor = resolveBidFloor(imp, bidRequest); + + return imp.toBuilder() + .bidfloor(resolvedBidFloor.getValue()) + .bidfloorcur(resolvedBidFloor.getCurrency()) + .build(); + } + + private Price resolveBidFloor(Imp imp, BidRequest bidRequest) { + final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); + return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, DEFAULT_BID_CURRENCY) + ? convertBidFloor(initialBidFloorPrice, bidRequest) + : initialBidFloorPrice; + } + + private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) { + final BigDecimal convertedPrice = currencyConversionService.convertCurrency( + bidFloorPrice.getValue(), + bidRequest, + bidFloorPrice.getCurrency(), + DEFAULT_BID_CURRENCY); + + return Price.of(DEFAULT_BID_CURRENCY, convertedPrice); } - public boolean extractTestMode(Imp imp) { + private ExtImpKobler parseImpExt(Imp imp) { try { - final ExtPrebid extPrebid = mapper.mapper().convertValue(imp.getExt(), - KOBLER_EXT_TYPE_REFERENCE); - final ExtImpKobler extImpKobler = extPrebid != null ? extPrebid.getBidder() : null; - return extImpKobler != null && Boolean.TRUE.equals(extImpKobler.getTest()); + return mapper.mapper().convertValue(imp.getExt(), KOBLER_EXT_TYPE_REFERENCE).getBidder(); } catch (IllegalArgumentException e) { - return false; + throw new PreBidException(e.getMessage()); } } diff --git a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java index f9aa7f3e9e6..ab3ba547aa7 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -26,6 +26,7 @@ import org.prebid.server.proto.openrtb.ext.response.BidType; import java.math.BigDecimal; +import java.util.Collections; import java.util.List; import java.util.function.UnaryOperator; @@ -67,7 +68,7 @@ public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { .imp(singletonList(Imp.builder() .bidfloor(BigDecimal.ONE) .bidfloorcur("EUR") - .ext(mapper.createObjectNode()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false)))) .build())) .build(); @@ -78,8 +79,9 @@ public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { final Result>> result = target.makeHttpRequests(bidRequest); // Then - assertThat(result.getErrors()).hasSize(1) - .satisfies(errors -> assertThat(errors.get(0).getMessage()).contains("Currency conversion failed")); + assertThat(result.getErrors()).hasSize(2) + .extracting(BidderError::getMessage) + .containsExactlyInAnyOrder("Currency conversion failed", "No valid impressions"); } @Test @@ -100,7 +102,9 @@ public void makeHttpRequestsShouldConvertBidFloorCurrency() { // Given final BidRequest bidRequest = givenBidRequest(imp -> imp .bidfloor(BigDecimal.ONE) - .bidfloorcur("EUR")); + .bidfloorcur("EUR") + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))); + when(currencyConversionService.convertCurrency(any(), any(), any(), any())) .thenReturn(BigDecimal.TEN); @@ -132,8 +136,11 @@ public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { @Test public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { // Given - final BidRequest bidRequest = givenBidRequest(identity()) - .toBuilder().cur(singletonList("EUR")).build(); + final BidRequest bidRequest = givenBidRequest(imp -> imp + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))) + .toBuilder() + .cur(singletonList("EUR")) + .build(); // When final Result>> result = target.makeHttpRequests(bidRequest); @@ -187,17 +194,30 @@ public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingExce } @Test - public void extractTestModeShouldReturnTrueWhenImpExtHasTestTrue() { - // Given + public void makeHttpRequestsShouldUseDevEndpointWhenImpExtTestIsTrue() { + // given + final ObjectNode extNode = jacksonMapper.mapper().createObjectNode(); + extNode.putObject("bidder").put("test", true); + final Imp imp = Imp.builder() - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true)))) + .id("test-imp") + .ext(extNode) .build(); - // When - final boolean testMode = target.extractTestMode(imp); + final BidRequest bidRequest = BidRequest.builder() + .imp(Collections.singletonList(imp)) + .cur(Collections.singletonList("USD")) + .build(); - // Then - assertThat(testMode).isTrue(); + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + + final HttpRequest httpRequest = result.getValue().get(0); + assertThat(httpRequest.getUri()).isEqualTo(DEV_ENDPOINT); } private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { From 3078b30a90c6cf64454e15dce24bbd4af265e975 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 31 Jan 2025 15:08:00 +0100 Subject: [PATCH 10/13] fix checkstyle --- .../java/org/prebid/server/bidder/kobler/KoblerBidderTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java index ab3ba547aa7..78a58cf9d58 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -105,7 +105,6 @@ public void makeHttpRequestsShouldConvertBidFloorCurrency() { .bidfloorcur("EUR") .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))); - when(currencyConversionService.convertCurrency(any(), any(), any(), any())) .thenReturn(BigDecimal.TEN); From d64f1345ad5daae6ed034ef5f8d6dc71177cfa03 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 31 Jan 2025 16:55:57 +0100 Subject: [PATCH 11/13] fix comments --- .../server/bidder/kobler/KoblerBidder.java | 4 +- .../bidder/kobler/KoblerBidderTest.java | 106 +++++------------- 2 files changed, 29 insertions(+), 81 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java index efe57550397..86c78dd1e8b 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -63,9 +63,7 @@ public Result>> makeHttpRequests(BidRequest bidRequ boolean testMode = false; final List modifiedImps = new ArrayList<>(); - final List currencies = bidRequest.getCur() != null - ? new ArrayList<>(bidRequest.getCur()) - : new ArrayList<>(); + final List currencies = new ArrayList<>(bidRequest.getCur()); if (!currencies.contains(DEFAULT_BID_CURRENCY)) { currencies.add(DEFAULT_BID_CURRENCY); } diff --git a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java index 78a58cf9d58..05f7d273ccb 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -57,72 +57,56 @@ public void setUp() { @Test public void creationShouldFailOnInvalidEndpointUrl() { - assertThatIllegalArgumentException().isThrownBy(() -> new KoblerBidder( - "invalid_url", currencyConversionService, jacksonMapper)); + assertThatIllegalArgumentException().isThrownBy(() -> new KoblerBidder("invalid_url", currencyConversionService, jacksonMapper)); } @Test public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { // Given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .bidfloor(BigDecimal.ONE) - .bidfloorcur("EUR") - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false)))) - .build())) - .build(); + final BidRequest bidRequest = BidRequest.builder().cur(singletonList("USD")).imp(singletonList(Imp.builder().bidfloor(BigDecimal.ONE).bidfloorcur("EUR").ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false)))).build())).build(); - when(currencyConversionService.convertCurrency(any(), any(), any(), any())) - .thenThrow(new PreBidException("Currency conversion failed")); + when(currencyConversionService.convertCurrency(any(), any(), any(), any())).thenThrow(new PreBidException("Currency conversion failed")); // When final Result>> result = target.makeHttpRequests(bidRequest); // Then - assertThat(result.getErrors()).hasSize(2) - .extracting(BidderError::getMessage) - .containsExactlyInAnyOrder("Currency conversion failed", "No valid impressions"); + assertThat(result.getErrors()).hasSize(2).extracting(BidderError::getMessage).containsExactlyInAnyOrder("Currency conversion failed", "No valid impressions"); } @Test public void makeHttpRequestsShouldReturnErrorIfNoImps() { // Given - final BidRequest bidRequest = BidRequest.builder().imp(emptyList()).build(); + final BidRequest bidRequest = BidRequest.builder().cur(singletonList("USD")).imp(emptyList()).build(); // When final Result>> result = target.makeHttpRequests(bidRequest); // Then - assertThat(result.getErrors()).hasSize(1) - .allSatisfy(error -> assertThat(error.getMessage()).contains("No valid impressions")); + assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> assertThat(error.getMessage()).contains("No valid impressions")); } @Test public void makeHttpRequestsShouldConvertBidFloorCurrency() { // Given - final BidRequest bidRequest = givenBidRequest(imp -> imp - .bidfloor(BigDecimal.ONE) - .bidfloorcur("EUR") - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))); + final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder.cur(singletonList("EUR")), imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("EUR").ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))); - when(currencyConversionService.convertCurrency(any(), any(), any(), any())) - .thenReturn(BigDecimal.TEN); + when(currencyConversionService.convertCurrency(any(), any(), any(), any())).thenReturn(BigDecimal.TEN); // When final Result>> result = target.makeHttpRequests(bidRequest); // Then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue().get(0).getPayload().getImp()) - .extracting(Imp::getBidfloor, Imp::getBidfloorcur) - .containsExactly(tuple(BigDecimal.TEN, "USD")); + assertThat(result.getValue().get(0).getPayload().getImp()).extracting(Imp::getBidfloor, Imp::getBidfloorcur).containsExactly(tuple(BigDecimal.TEN, "USD")); } @Test public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { // Given - final BidRequest bidRequest = givenBidRequest(imp -> imp - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true))))); + final BidRequest bidRequest = givenBidRequest( + bidRequestBuilder -> bidRequestBuilder.cur(singletonList("EUR")), + imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true))))); // When final Result>> result = target.makeHttpRequests(bidRequest); @@ -135,18 +119,13 @@ public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { @Test public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { // Given - final BidRequest bidRequest = givenBidRequest(imp -> imp - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))) - .toBuilder() - .cur(singletonList("EUR")) - .build(); + final BidRequest bidRequest = givenBidRequest(imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))).toBuilder().cur(singletonList("EUR")).build(); // When final Result>> result = target.makeHttpRequests(bidRequest); // Then - assertThat(result.getValue().get(0).getPayload().getCur()) - .containsExactlyInAnyOrder("EUR", "USD"); + assertThat(result.getValue().get(0).getPayload().getCur()).containsExactlyInAnyOrder("EUR", "USD"); } @Test @@ -158,38 +137,25 @@ public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); // Then - assertThat(result.getErrors()).hasSize(1) - .allSatisfy(error -> { - assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); - assertThat(error.getMessage()).startsWith("Failed to decode"); - }); + assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(error.getMessage()).startsWith("Failed to decode"); + }); } @Test public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingException { // Given - final ObjectNode bidExt = mapper.createObjectNode() - .set("prebid", mapper.createObjectNode().put("type", "banner")); - - final BidderCall httpCall = givenHttpCall( - BidResponse.builder() - .cur("USD") - .seatbid(singletonList(SeatBid.builder() - .bid(singletonList(Bid.builder() - .impid("123") - .ext(bidExt) - .build())) - .build())) - .build()); + final ObjectNode bidExt = mapper.createObjectNode().set("prebid", mapper.createObjectNode().put("type", "banner")); + + final BidderCall httpCall = givenHttpCall(BidResponse.builder().cur("USD").seatbid(singletonList(SeatBid.builder().bid(singletonList(Bid.builder().impid("123").ext(bidExt).build())).build())).build()); // When final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); // Then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(BidderBid::getType) - .containsExactly(BidType.banner); + assertThat(result.getValue()).extracting(BidderBid::getType).containsExactly(BidType.banner); } @Test @@ -198,15 +164,9 @@ public void makeHttpRequestsShouldUseDevEndpointWhenImpExtTestIsTrue() { final ObjectNode extNode = jacksonMapper.mapper().createObjectNode(); extNode.putObject("bidder").put("test", true); - final Imp imp = Imp.builder() - .id("test-imp") - .ext(extNode) - .build(); + final Imp imp = Imp.builder().id("test-imp").ext(extNode).build(); - final BidRequest bidRequest = BidRequest.builder() - .imp(Collections.singletonList(imp)) - .cur(Collections.singletonList("USD")) - .build(); + final BidRequest bidRequest = BidRequest.builder().imp(Collections.singletonList(imp)).cur(Collections.singletonList("USD")).build(); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -223,26 +183,16 @@ private static BidRequest givenBidRequest(UnaryOperator impCusto return givenBidRequest(identity(), impCustomizer); } - private static BidRequest givenBidRequest( - UnaryOperator bidRequestCustomizer, - UnaryOperator impCustomizer) { + private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, UnaryOperator impCustomizer) { - return bidRequestCustomizer.apply(BidRequest.builder() - .imp(singletonList(impCustomizer.apply(Imp.builder().id("123")).build()))) - .build(); + return bidRequestCustomizer.apply(BidRequest.builder().imp(singletonList(impCustomizer.apply(Imp.builder().id("123")).build()))).build(); } private BidderCall givenHttpCall(BidResponse bidResponse) throws JsonProcessingException { - return BidderCall.succeededHttp( - HttpRequest.builder().payload(null).build(), - HttpResponse.of(200, null, mapper.writeValueAsString(bidResponse)), - null); + return BidderCall.succeededHttp(HttpRequest.builder().payload(null).build(), HttpResponse.of(200, null, mapper.writeValueAsString(bidResponse)), null); } private BidderCall givenHttpCall() { - return BidderCall.succeededHttp( - HttpRequest.builder().payload(null).build(), - HttpResponse.of(200, null, "invalid"), - null); + return BidderCall.succeededHttp(HttpRequest.builder().payload(null).build(), HttpResponse.of(200, null, "invalid"), null); } } From 68770b03be768a0c0e9ed0c2db9aa6eb7626911f Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 31 Jan 2025 17:13:53 +0100 Subject: [PATCH 12/13] fix checkstyle --- .../bidder/kobler/KoblerBidderTest.java | 66 ++++++++++++++----- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java index 05f7d273ccb..0223f319d36 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -57,21 +57,31 @@ public void setUp() { @Test public void creationShouldFailOnInvalidEndpointUrl() { - assertThatIllegalArgumentException().isThrownBy(() -> new KoblerBidder("invalid_url", currencyConversionService, jacksonMapper)); + assertThatIllegalArgumentException().isThrownBy(() -> + new KoblerBidder("invalid_url", currencyConversionService, jacksonMapper)); } @Test public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { // Given - final BidRequest bidRequest = BidRequest.builder().cur(singletonList("USD")).imp(singletonList(Imp.builder().bidfloor(BigDecimal.ONE).bidfloorcur("EUR").ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false)))).build())).build(); + final BidRequest bidRequest = BidRequest.builder() + .cur(singletonList("USD")) + .imp(singletonList(Imp.builder() + .bidfloor(BigDecimal.ONE) + .bidfloorcur("EUR") + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false)))).build())).build(); - when(currencyConversionService.convertCurrency(any(), any(), any(), any())).thenThrow(new PreBidException("Currency conversion failed")); + when(currencyConversionService.convertCurrency(any(), any(), any(), any())) + .thenThrow(new PreBidException("Currency conversion failed")); // When final Result>> result = target.makeHttpRequests(bidRequest); // Then - assertThat(result.getErrors()).hasSize(2).extracting(BidderError::getMessage).containsExactlyInAnyOrder("Currency conversion failed", "No valid impressions"); + assertThat(result.getErrors()) + .hasSize(2) + .extracting(BidderError::getMessage) + .containsExactlyInAnyOrder("Currency conversion failed", "No valid impressions"); } @Test @@ -83,13 +93,18 @@ public void makeHttpRequestsShouldReturnErrorIfNoImps() { final Result>> result = target.makeHttpRequests(bidRequest); // Then - assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> assertThat(error.getMessage()).contains("No valid impressions")); + assertThat(result.getErrors()) + .hasSize(1) + .allSatisfy(error -> assertThat(error.getMessage()).contains("No valid impressions")); } @Test public void makeHttpRequestsShouldConvertBidFloorCurrency() { // Given - final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder.cur(singletonList("EUR")), imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("EUR").ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))); + final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder + .cur(singletonList("EUR")), + imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("EUR") + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))); when(currencyConversionService.convertCurrency(any(), any(), any(), any())).thenReturn(BigDecimal.TEN); @@ -98,14 +113,16 @@ public void makeHttpRequestsShouldConvertBidFloorCurrency() { // Then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue().get(0).getPayload().getImp()).extracting(Imp::getBidfloor, Imp::getBidfloorcur).containsExactly(tuple(BigDecimal.TEN, "USD")); + assertThat(result.getValue().get(0).getPayload().getImp()) + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsExactly(tuple(BigDecimal.TEN, "USD")); } @Test public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { // Given - final BidRequest bidRequest = givenBidRequest( - bidRequestBuilder -> bidRequestBuilder.cur(singletonList("EUR")), + final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder + .cur(singletonList("EUR")), imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true))))); // When @@ -119,7 +136,9 @@ public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { @Test public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { // Given - final BidRequest bidRequest = givenBidRequest(imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))).toBuilder().cur(singletonList("EUR")).build(); + final BidRequest bidRequest = givenBidRequest(imp -> + imp.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))).toBuilder() + .cur(singletonList("EUR")).build(); // When final Result>> result = target.makeHttpRequests(bidRequest); @@ -146,9 +165,14 @@ public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { @Test public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingException { // Given - final ObjectNode bidExt = mapper.createObjectNode().set("prebid", mapper.createObjectNode().put("type", "banner")); + final ObjectNode bidExt = mapper.createObjectNode() + .set("prebid", mapper.createObjectNode().put("type", "banner")); - final BidderCall httpCall = givenHttpCall(BidResponse.builder().cur("USD").seatbid(singletonList(SeatBid.builder().bid(singletonList(Bid.builder().impid("123").ext(bidExt).build())).build())).build()); + final BidderCall httpCall = givenHttpCall(BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(Bid.builder() + .impid("123").ext(bidExt).build())).build())).build()); // When final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); @@ -166,7 +190,8 @@ public void makeHttpRequestsShouldUseDevEndpointWhenImpExtTestIsTrue() { final Imp imp = Imp.builder().id("test-imp").ext(extNode).build(); - final BidRequest bidRequest = BidRequest.builder().imp(Collections.singletonList(imp)).cur(Collections.singletonList("USD")).build(); + final BidRequest bidRequest = BidRequest.builder() + .imp(Collections.singletonList(imp)).cur(Collections.singletonList("USD")).build(); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -183,16 +208,21 @@ private static BidRequest givenBidRequest(UnaryOperator impCusto return givenBidRequest(identity(), impCustomizer); } - private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, UnaryOperator impCustomizer) { - - return bidRequestCustomizer.apply(BidRequest.builder().imp(singletonList(impCustomizer.apply(Imp.builder().id("123")).build()))).build(); + private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, + UnaryOperator impCustomizer) { + return bidRequestCustomizer.apply(BidRequest.builder() + .imp(singletonList(impCustomizer.apply(Imp.builder().id("123")).build()))).build(); } private BidderCall givenHttpCall(BidResponse bidResponse) throws JsonProcessingException { - return BidderCall.succeededHttp(HttpRequest.builder().payload(null).build(), HttpResponse.of(200, null, mapper.writeValueAsString(bidResponse)), null); + return BidderCall.succeededHttp( + HttpRequest.builder().payload(null).build(), + HttpResponse.of(200, null, mapper.writeValueAsString(bidResponse)), null); } private BidderCall givenHttpCall() { - return BidderCall.succeededHttp(HttpRequest.builder().payload(null).build(), HttpResponse.of(200, null, "invalid"), null); + return BidderCall.succeededHttp( + HttpRequest.builder().payload(null).build(), + HttpResponse.of(200, null, "invalid"), null); } } From 4982cf485a015ae1845f43f5080f82552ff91307 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Mon, 10 Feb 2025 13:31:20 +0100 Subject: [PATCH 13/13] fix comments --- .../server/bidder/kobler/KoblerBidder.java | 54 ++++++------------- .../bidder/kobler/KoblerBidderTest.java | 39 ++++++++++---- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java index 86c78dd1e8b..bb95fbffecb 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -19,7 +19,6 @@ import org.prebid.server.currency.CurrencyConversionService; import org.prebid.server.exception.PreBidException; import org.prebid.server.json.DecodeException; -import org.prebid.server.json.EncodeException; import org.prebid.server.json.JacksonMapper; import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.kobler.ExtImpKobler; @@ -34,6 +33,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; public class KoblerBidder implements Bidder { @@ -71,26 +71,21 @@ public Result>> makeHttpRequests(BidRequest bidRequ final List imps = bidRequest.getImp(); if (!imps.isEmpty()) { try { - testMode = parseImpExt(imps.get(0)).getTest(); + testMode = parseImpExt(imps.getFirst()).getTest(); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); + return Result.withErrors(errors); } } for (Imp imp : imps) { try { - final Imp processedImp = processImp(bidRequest, imp); - modifiedImps.add(processedImp); + modifiedImps.add(processImp(bidRequest, imp)); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); } } - if (modifiedImps.isEmpty()) { - errors.add(BidderError.badInput("No valid impressions")); - return Result.withErrors(errors); - } - final BidRequest modifiedRequest = bidRequest.toBuilder() .cur(currencies) .imp(modifiedImps) @@ -98,13 +93,8 @@ public Result>> makeHttpRequests(BidRequest bidRequ final String endpoint = testMode ? DEV_ENDPOINT : endpointUrl; - try { - final HttpRequest httpRequest = BidderUtil.defaultRequest(modifiedRequest, endpoint, mapper); - return Result.of(Collections.singletonList(httpRequest), errors); - } catch (EncodeException e) { - errors.add(BidderError.badInput("Failed to encode request: " + e.getMessage())); - return Result.withErrors(errors); - } + final HttpRequest httpRequest = BidderUtil.defaultRequest(modifiedRequest, endpoint, mapper); + return Result.of(Collections.singletonList(httpRequest), errors); } private Imp processImp(BidRequest bidRequest, Imp imp) { @@ -146,46 +136,34 @@ public Result> makeBids(BidderCall httpCall, BidRequ try { final List errors = new ArrayList<>(); final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return Result.of(extractBids(bidResponse, errors), errors); + return Result.of(extractBids(bidResponse), errors); } catch (DecodeException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private List extractBids(BidResponse bidResponse, List errors) { + private List extractBids(BidResponse bidResponse) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { return Collections.emptyList(); } - return bidsFromResponse(bidResponse, errors); + return bidsFromResponse(bidResponse); } - private List bidsFromResponse(BidResponse bidResponse, List errors) { + private List bidsFromResponse(BidResponse bidResponse) { return bidResponse.getSeatbid().stream() - .filter(Objects::nonNull) .map(SeatBid::getBid) - .filter(Objects::nonNull) .flatMap(Collection::stream) .map(bid -> BidderBid.of(bid, getBidType(bid), bidResponse.getCur())) - .filter(Objects::nonNull) .toList(); } private BidType getBidType(Bid bid) { - if (bid.getExt() == null) { - return BidType.banner; - } - - final ObjectNode prebidNode = (ObjectNode) bid.getExt().get(EXT_PREBID); - if (prebidNode == null) { - return BidType.banner; - } - - final ExtBidPrebid extBidPrebid = parseExtBidPrebid(prebidNode); - if (extBidPrebid == null || extBidPrebid.getType() == null) { - return BidType.banner; - } - - return extBidPrebid.getType(); // jeśli udało się sparsować + return Optional.ofNullable(bid.getExt()) + .map(ext -> ext.get(EXT_PREBID)) + .map(ObjectNode.class::cast) + .map(this::parseExtBidPrebid) + .map(ExtBidPrebid::getType) + .orElse(BidType.banner); } private ExtBidPrebid parseExtBidPrebid(ObjectNode prebid) { diff --git a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java index 0223f319d36..2da2590507a 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -30,7 +30,6 @@ import java.util.List; import java.util.function.UnaryOperator; -import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; @@ -79,23 +78,38 @@ public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { // Then assertThat(result.getErrors()) - .hasSize(2) + .hasSize(1) .extracting(BidderError::getMessage) - .containsExactlyInAnyOrder("Currency conversion failed", "No valid impressions"); + .containsExactlyInAnyOrder("Currency conversion failed"); } @Test - public void makeHttpRequestsShouldReturnErrorIfNoImps() { + public void makeBidsShouldReturnEmptyListWhenBidResponseIsNull() { // Given - final BidRequest bidRequest = BidRequest.builder().cur(singletonList("USD")).imp(emptyList()).build(); + final BidderCall httpCall = givenHttpCallWithBody("null"); // When - final Result>> result = target.makeHttpRequests(bidRequest); + final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); // Then - assertThat(result.getErrors()) - .hasSize(1) - .allSatisfy(error -> assertThat(error.getMessage()).contains("No valid impressions")); + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListWhenSeatbidIsEmpty() throws JsonProcessingException { + // Given + final BidResponse bidResponse = BidResponse.builder() + .seatbid(Collections.emptyList()) + .build(); + final BidderCall httpCall = givenHttpCall(bidResponse); + + // When + final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); + + // Then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).isEmpty(); } @Test @@ -225,4 +239,11 @@ private BidderCall givenHttpCall() { HttpRequest.builder().payload(null).build(), HttpResponse.of(200, null, "invalid"), null); } + + private BidderCall givenHttpCallWithBody(String body) { + return BidderCall.succeededHttp( + HttpRequest.builder().payload(null).build(), + HttpResponse.of(200, null, body), + null); + } }