From 8ea1419f54c20b7efbd93923ea7b41c068abb585 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 17 Jan 2025 14:35:16 +0100 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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); + } } From 3521a00308b43d0cd22a65883f00a4fb9585dcd3 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Thu, 13 Feb 2025 14:47:24 +0100 Subject: [PATCH 14/20] fix comments --- .../server/bidder/kobler/KoblerBidder.java | 10 +- .../config/bidder/KoblerConfiguration.java | 6 +- .../bidder/kobler/KoblerBidderTest.java | 98 +++++++++---------- 3 files changed, 58 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 bb95fbffecb..01385356662 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -80,9 +80,10 @@ public Result>> makeHttpRequests(BidRequest bidRequ for (Imp imp : imps) { try { - modifiedImps.add(processImp(bidRequest, imp)); + modifiedImps.add(modifyImp(bidRequest, imp)); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); + return Result.withErrors(errors); } } @@ -97,7 +98,7 @@ public Result>> makeHttpRequests(BidRequest bidRequ return Result.of(Collections.singletonList(httpRequest), errors); } - private Imp processImp(BidRequest bidRequest, Imp imp) { + private Imp modifyImp(BidRequest bidRequest, Imp imp) { final Price resolvedBidFloor = resolveBidFloor(imp, bidRequest); return imp.toBuilder() @@ -134,9 +135,8 @@ private ExtImpKobler parseImpExt(Imp imp) { @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); + return Result.withValues(extractBids(bidResponse)); } catch (DecodeException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } @@ -151,7 +151,9 @@ private List extractBids(BidResponse bidResponse) { 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())) .toList(); 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 a82e7115256..6c0f2b5d3fa 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 @@ -30,9 +30,9 @@ BidderConfigurationProperties configurationProperties() { @Bean BidderDeps koblerBidderDeps(BidderConfigurationProperties koblerConfigurationProperties, - CurrencyConversionService currencyConversionService, - @NotBlank @Value("${external-url}") String externalUrl, - JacksonMapper mapper) { + CurrencyConversionService currencyConversionService, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(koblerConfigurationProperties) 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 2da2590507a..ad593c5e62b 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -62,7 +62,7 @@ public void creationShouldFailOnInvalidEndpointUrl() { @Test public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { - // Given + // given final BidRequest bidRequest = BidRequest.builder() .cur(singletonList("USD")) .imp(singletonList(Imp.builder() @@ -73,48 +73,19 @@ public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { when(currencyConversionService.convertCurrency(any(), any(), any(), any())) .thenThrow(new PreBidException("Currency conversion failed")); - // When + // when final Result>> result = target.makeHttpRequests(bidRequest); - // Then + // then assertThat(result.getErrors()) .hasSize(1) .extracting(BidderError::getMessage) .containsExactlyInAnyOrder("Currency conversion failed"); } - @Test - public void makeBidsShouldReturnEmptyListWhenBidResponseIsNull() { - // Given - final BidderCall httpCall = givenHttpCallWithBody("null"); - - // When - final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); - - // Then - 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 public void makeHttpRequestsShouldConvertBidFloorCurrency() { - // Given + // given final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder .cur(singletonList("EUR")), imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("EUR") @@ -122,10 +93,10 @@ public void makeHttpRequestsShouldConvertBidFloorCurrency() { when(currencyConversionService.convertCurrency(any(), any(), any(), any())).thenReturn(BigDecimal.TEN); - // When + // when final Result>> result = target.makeHttpRequests(bidRequest); - // Then + // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue().get(0).getPayload().getImp()) .extracting(Imp::getBidfloor, Imp::getBidfloorcur) @@ -133,43 +104,43 @@ public void makeHttpRequestsShouldConvertBidFloorCurrency() { } @Test - public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { - // Given + public void makeHttpRequestsShouldUseDevEndpointwhenTestModeEnabled() { + // given final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder .cur(singletonList("EUR")), imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true))))); - // When + // when final Result>> result = target.makeHttpRequests(bidRequest); - // Then + // then assertThat(result.getValue()).hasSize(1); assertThat(result.getValue().get(0).getUri()).isEqualTo(DEV_ENDPOINT); } @Test public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { - // Given + // given final BidRequest bidRequest = givenBidRequest(imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))).toBuilder() .cur(singletonList("EUR")).build(); - // When + // when final Result>> result = target.makeHttpRequests(bidRequest); - // Then + // then assertThat(result.getValue().get(0).getPayload().getCur()).containsExactlyInAnyOrder("EUR", "USD"); } @Test public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { - // Given + // given final BidderCall httpCall = givenHttpCall(); - // When + // when final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); - // Then + // then assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> { assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); assertThat(error.getMessage()).startsWith("Failed to decode"); @@ -178,7 +149,7 @@ public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { @Test public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingException { - // Given + // given final ObjectNode bidExt = mapper.createObjectNode() .set("prebid", mapper.createObjectNode().put("type", "banner")); @@ -188,16 +159,45 @@ public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingExce .bid(singletonList(Bid.builder() .impid("123").ext(bidExt).build())).build())).build()); - // When + // when final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); - // Then + // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).extracting(BidderBid::getType).containsExactly(BidType.banner); } @Test - public void makeHttpRequestsShouldUseDevEndpointWhenImpExtTestIsTrue() { + public void makeBidsShouldReturnEmptyListwhenBidResponseIsNull() { + // given + final BidderCall httpCall = givenHttpCallWithBody("null"); + + // when + final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); + + // then + 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 + public void makeHttpRequestsShouldUseDevEndpointwhenImpExtTestIsTrue() { // given final ObjectNode extNode = jacksonMapper.mapper().createObjectNode(); extNode.putObject("bidder").put("test", true); From 5ee228355ad94fdc2e23ac178ccf56e02dfa1650 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 21 Feb 2025 12:33:29 +0100 Subject: [PATCH 15/20] fix comments --- .../server/bidder/kobler/KoblerBidder.java | 59 ++++++++++------ .../ext/request/kobler/ExtImpKobler.java | 2 - .../config/bidder/KoblerConfiguration.java | 39 +++++++++-- src/main/resources/bidder-config/kobler.yaml | 3 + .../bidder/kobler/KoblerBidderTest.java | 68 ++++++++++++------- 5 files changed, 116 insertions(+), 55 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 01385356662..e0b76ff7d26 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; @@ -40,19 +41,23 @@ 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"; - private final String endpointUrl; + private final String defaultBidCurrency; + private final String devEndpoint; + private final String extPrebid; private final CurrencyConversionService currencyConversionService; private final JacksonMapper mapper; public KoblerBidder(String endpointUrl, + String defaultBidCurrency, + String devEndpoint, + String extPrebid, CurrencyConversionService currencyConversionService, JacksonMapper mapper) { - this.endpointUrl = HttpUtil.validateUrl(endpointUrl); + this.defaultBidCurrency = Objects.requireNonNull(defaultBidCurrency); + this.devEndpoint = Objects.requireNonNull(devEndpoint); + this.extPrebid = Objects.requireNonNull(extPrebid); this.currencyConversionService = Objects.requireNonNull(currencyConversionService); this.mapper = Objects.requireNonNull(mapper); } @@ -63,19 +68,13 @@ public Result>> makeHttpRequests(BidRequest bidRequ boolean testMode = false; final List modifiedImps = new ArrayList<>(); - final List currencies = new ArrayList<>(bidRequest.getCur()); - if (!currencies.contains(DEFAULT_BID_CURRENCY)) { - currencies.add(DEFAULT_BID_CURRENCY); - } + final List currencies = normalizeCurrencies(bidRequest); final List imps = bidRequest.getImp(); - if (!imps.isEmpty()) { - try { - testMode = parseImpExt(imps.getFirst()).getTest(); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); - return Result.withErrors(errors); - } + try { + testMode = isTest(imps.getFirst()); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); } for (Imp imp : imps) { @@ -92,12 +91,28 @@ public Result>> makeHttpRequests(BidRequest bidRequ .imp(modifiedImps) .build(); - final String endpoint = testMode ? DEV_ENDPOINT : endpointUrl; + final String endpoint = testMode ? devEndpoint : endpointUrl; final HttpRequest httpRequest = BidderUtil.defaultRequest(modifiedRequest, endpoint, mapper); return Result.of(Collections.singletonList(httpRequest), errors); } + private boolean isTest(Imp imp) { + try { + return parseImpExt(imp).getTest(); + } catch (PreBidException e) { + return false; + } + } + + private List normalizeCurrencies(BidRequest bidRequest) { + final List currencies = new ArrayList<>(bidRequest.getCur()); + if (!currencies.contains(defaultBidCurrency)) { + currencies.add(defaultBidCurrency); + } + return currencies; + } + private Imp modifyImp(BidRequest bidRequest, Imp imp) { final Price resolvedBidFloor = resolveBidFloor(imp, bidRequest); @@ -109,7 +124,7 @@ private Imp modifyImp(BidRequest bidRequest, Imp imp) { private Price resolveBidFloor(Imp imp, BidRequest bidRequest) { final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); - return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, DEFAULT_BID_CURRENCY) + return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, defaultBidCurrency) ? convertBidFloor(initialBidFloorPrice, bidRequest) : initialBidFloorPrice; } @@ -119,9 +134,9 @@ private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) { bidFloorPrice.getValue(), bidRequest, bidFloorPrice.getCurrency(), - DEFAULT_BID_CURRENCY); + defaultBidCurrency); - return Price.of(DEFAULT_BID_CURRENCY, convertedPrice); + return Price.of(defaultBidCurrency, convertedPrice); } private ExtImpKobler parseImpExt(Imp imp) { @@ -155,13 +170,15 @@ private List bidsFromResponse(BidResponse bidResponse) { .map(SeatBid::getBid) .filter(Objects::nonNull) .flatMap(Collection::stream) + .filter(Objects::nonNull) .map(bid -> BidderBid.of(bid, getBidType(bid), bidResponse.getCur())) .toList(); } private BidType getBidType(Bid bid) { return Optional.ofNullable(bid.getExt()) - .map(ext -> ext.get(EXT_PREBID)) + .map(ext -> ext.get(extPrebid)) + .filter(JsonNode::isObject) .map(ObjectNode.class::cast) .map(this::parseExtBidPrebid) .map(ExtBidPrebid::getType) 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 dead820b18b..ba8e94bb6a9 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,11 +1,9 @@ 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/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/KoblerConfiguration.java index 6c0f2b5d3fa..1517e54a36c 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,5 +1,8 @@ package org.prebid.server.spring.config.bidder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import org.prebid.server.bidder.BidderDeps; import org.prebid.server.bidder.kobler.KoblerBidder; import org.prebid.server.currency.CurrencyConversionService; @@ -13,6 +16,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.validation.annotation.Validated; import jakarta.validation.constraints.NotBlank; @@ -24,20 +28,43 @@ public class KoblerConfiguration { @Bean("koblerConfigurationProperties") @ConfigurationProperties("adapters.kobler") - BidderConfigurationProperties configurationProperties() { - return new BidderConfigurationProperties(); + KoblerConfigurationProperties configurationProperties() { + return new KoblerConfigurationProperties(); } @Bean - BidderDeps koblerBidderDeps(BidderConfigurationProperties koblerConfigurationProperties, + BidderDeps koblerBidderDeps(KoblerConfigurationProperties config, CurrencyConversionService currencyConversionService, @NotBlank @Value("${external-url}") String externalUrl, JacksonMapper mapper) { - return BidderDepsAssembler.forBidder(BIDDER_NAME) - .withConfig(koblerConfigurationProperties) + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(config) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new KoblerBidder(config.getEndpoint(), currencyConversionService, mapper)) + .bidderCreator(cfg -> new KoblerBidder( + cfg.getEndpoint(), + cfg.getDefaultBidCurrency(), + cfg.getDevEndpoint(), + cfg.getExtPrebid(), + currencyConversionService, + mapper)) .assemble(); + + } + + @Validated + @Data + @EqualsAndHashCode(callSuper = true) + @NoArgsConstructor + private static class KoblerConfigurationProperties extends BidderConfigurationProperties { + + @NotBlank + private String defaultBidCurrency; + + @NotBlank + private String devEndpoint; + + @NotBlank + private String extPrebid; } } diff --git a/src/main/resources/bidder-config/kobler.yaml b/src/main/resources/bidder-config/kobler.yaml index d01e7aa7318..7d256ec22ce 100644 --- a/src/main/resources/bidder-config/kobler.yaml +++ b/src/main/resources/bidder-config/kobler.yaml @@ -1,6 +1,9 @@ adapters: kobler: endpoint: "https://bid.essrtb.com/bid/prebid_server_rtb_call" + default-bid-currency: "USD" + dev-endpoint: "https://bid-service.dev.essrtb.com/bid/prebid_server_rtb_call" + ext-prebid: "prebid" endpoint-compression: gzip geoscope: - NOR 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 ad593c5e62b..9db717705f3 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -43,6 +43,8 @@ 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"; + private static final String DEFAULT_BID_CURRENCY = "USD"; + private static final String EXT_PREBID = "prebid"; @Mock private CurrencyConversionService currencyConversionService; @@ -51,13 +53,25 @@ public class KoblerBidderTest extends VertxTest { @BeforeEach public void setUp() { - target = new KoblerBidder(ENDPOINT_URL, currencyConversionService, jacksonMapper); + target = new KoblerBidder( + ENDPOINT_URL, + DEFAULT_BID_CURRENCY, + EXT_PREBID, + DEV_ENDPOINT, + currencyConversionService, + jacksonMapper); } @Test public void creationShouldFailOnInvalidEndpointUrl() { assertThatIllegalArgumentException().isThrownBy(() -> - new KoblerBidder("invalid_url", currencyConversionService, jacksonMapper)); + new KoblerBidder( + "invalid_url", + DEFAULT_BID_CURRENCY, + EXT_PREBID, + DEV_ENDPOINT, + currencyConversionService, + jacksonMapper)); } @Test @@ -98,13 +112,15 @@ public void makeHttpRequestsShouldConvertBidFloorCurrency() { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue().get(0).getPayload().getImp()) + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) .extracting(Imp::getBidfloor, Imp::getBidfloorcur) .containsExactly(tuple(BigDecimal.TEN, "USD")); } @Test - public void makeHttpRequestsShouldUseDevEndpointwhenTestModeEnabled() { + public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { // given final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder .cur(singletonList("EUR")), @@ -132,6 +148,28 @@ public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { assertThat(result.getValue().get(0).getPayload().getCur()).containsExactlyInAnyOrder("EUR", "USD"); } + @Test + public void makeHttpRequestsShouldUseDevEndpointwhenImpExtTestIsTrue() { + // given + final ObjectNode extNode = jacksonMapper.mapper().createObjectNode(); + extNode.putObject("bidder").put("test", true); + + 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(); + + // 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); + } + @Test public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { // given @@ -196,28 +234,6 @@ public void makeBidsShouldReturnEmptyListwhenSeatbidIsEmpty() throws JsonProcess assertThat(result.getErrors()).isEmpty(); } - @Test - public void makeHttpRequestsShouldUseDevEndpointwhenImpExtTestIsTrue() { - // given - final ObjectNode extNode = jacksonMapper.mapper().createObjectNode(); - extNode.putObject("bidder").put("test", true); - - 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(); - - // 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) { return givenBidRequest(identity(), impCustomizer); } From a8b9c4be1698356055c73b04f8f951136cb9dee2 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Fri, 21 Feb 2025 12:47:28 +0100 Subject: [PATCH 16/20] fix tests --- .../org/prebid/server/bidder/kobler/KoblerBidderTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 9db717705f3..1cc5846d624 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -56,8 +56,8 @@ public void setUp() { target = new KoblerBidder( ENDPOINT_URL, DEFAULT_BID_CURRENCY, - EXT_PREBID, DEV_ENDPOINT, + EXT_PREBID, currencyConversionService, jacksonMapper); } @@ -68,8 +68,8 @@ public void creationShouldFailOnInvalidEndpointUrl() { new KoblerBidder( "invalid_url", DEFAULT_BID_CURRENCY, - EXT_PREBID, DEV_ENDPOINT, + EXT_PREBID, currencyConversionService, jacksonMapper)); } From 0ea93c22d056d4c36eb203897f76d8d18735e682 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Mon, 17 Mar 2025 13:04:13 +0100 Subject: [PATCH 17/20] fix comments --- src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java | 1 + 1 file changed, 1 insertion(+) 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 e0b76ff7d26..09179d0a5d7 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -180,6 +180,7 @@ private BidType getBidType(Bid bid) { .map(ext -> ext.get(extPrebid)) .filter(JsonNode::isObject) .map(ObjectNode.class::cast) + .filter(JsonNode::isObject) .map(this::parseExtBidPrebid) .map(ExtBidPrebid::getType) .orElse(BidType.banner); From 1ffdb2315c48d5666eca3108e2f3a09435fc7332 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Wed, 2 Apr 2025 11:28:02 +0200 Subject: [PATCH 18/20] fix comments --- .../server/bidder/kobler/KoblerBidder.java | 22 +++++++++---------- .../config/bidder/KoblerConfiguration.java | 8 ------- src/main/resources/bidder-config/kobler.yaml | 2 -- .../bidder/kobler/KoblerBidderTest.java | 12 +++------- 4 files changed, 13 insertions(+), 31 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 09179d0a5d7..8bfa9211441 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -41,23 +41,21 @@ public class KoblerBidder implements Bidder { private static final TypeReference> KOBLER_EXT_TYPE_REFERENCE = new TypeReference<>() { }; + + private static final String DEFAULT_BID_CURRENCY = "USD"; + private static final String EXT_PREBID = "prebid"; + private final String endpointUrl; - private final String defaultBidCurrency; private final String devEndpoint; - private final String extPrebid; private final CurrencyConversionService currencyConversionService; private final JacksonMapper mapper; public KoblerBidder(String endpointUrl, - String defaultBidCurrency, String devEndpoint, - String extPrebid, CurrencyConversionService currencyConversionService, JacksonMapper mapper) { this.endpointUrl = HttpUtil.validateUrl(endpointUrl); - this.defaultBidCurrency = Objects.requireNonNull(defaultBidCurrency); this.devEndpoint = Objects.requireNonNull(devEndpoint); - this.extPrebid = Objects.requireNonNull(extPrebid); this.currencyConversionService = Objects.requireNonNull(currencyConversionService); this.mapper = Objects.requireNonNull(mapper); } @@ -107,8 +105,8 @@ private boolean isTest(Imp imp) { private List normalizeCurrencies(BidRequest bidRequest) { final List currencies = new ArrayList<>(bidRequest.getCur()); - if (!currencies.contains(defaultBidCurrency)) { - currencies.add(defaultBidCurrency); + if (!currencies.contains(DEFAULT_BID_CURRENCY)) { + currencies.add(DEFAULT_BID_CURRENCY); } return currencies; } @@ -124,7 +122,7 @@ private Imp modifyImp(BidRequest bidRequest, Imp imp) { private Price resolveBidFloor(Imp imp, BidRequest bidRequest) { final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); - return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, defaultBidCurrency) + return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, DEFAULT_BID_CURRENCY) ? convertBidFloor(initialBidFloorPrice, bidRequest) : initialBidFloorPrice; } @@ -134,9 +132,9 @@ private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) { bidFloorPrice.getValue(), bidRequest, bidFloorPrice.getCurrency(), - defaultBidCurrency); + DEFAULT_BID_CURRENCY); - return Price.of(defaultBidCurrency, convertedPrice); + return Price.of(DEFAULT_BID_CURRENCY, convertedPrice); } private ExtImpKobler parseImpExt(Imp imp) { @@ -177,7 +175,7 @@ private List bidsFromResponse(BidResponse bidResponse) { private BidType getBidType(Bid bid) { return Optional.ofNullable(bid.getExt()) - .map(ext -> ext.get(extPrebid)) + .map(ext -> ext.get(EXT_PREBID)) .filter(JsonNode::isObject) .map(ObjectNode.class::cast) .filter(JsonNode::isObject) 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 1517e54a36c..eaa41c79bde 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 @@ -43,9 +43,7 @@ BidderDeps koblerBidderDeps(KoblerConfigurationProperties config, .usersyncerCreator(UsersyncerCreator.create(externalUrl)) .bidderCreator(cfg -> new KoblerBidder( cfg.getEndpoint(), - cfg.getDefaultBidCurrency(), cfg.getDevEndpoint(), - cfg.getExtPrebid(), currencyConversionService, mapper)) .assemble(); @@ -58,13 +56,7 @@ BidderDeps koblerBidderDeps(KoblerConfigurationProperties config, @NoArgsConstructor private static class KoblerConfigurationProperties extends BidderConfigurationProperties { - @NotBlank - private String defaultBidCurrency; - @NotBlank private String devEndpoint; - - @NotBlank - private String extPrebid; } } diff --git a/src/main/resources/bidder-config/kobler.yaml b/src/main/resources/bidder-config/kobler.yaml index 7d256ec22ce..0adbb174ad8 100644 --- a/src/main/resources/bidder-config/kobler.yaml +++ b/src/main/resources/bidder-config/kobler.yaml @@ -1,9 +1,7 @@ adapters: kobler: endpoint: "https://bid.essrtb.com/bid/prebid_server_rtb_call" - default-bid-currency: "USD" dev-endpoint: "https://bid-service.dev.essrtb.com/bid/prebid_server_rtb_call" - ext-prebid: "prebid" endpoint-compression: gzip geoscope: - NOR 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 1cc5846d624..66eeadcd9aa 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -43,8 +43,6 @@ 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"; - private static final String DEFAULT_BID_CURRENCY = "USD"; - private static final String EXT_PREBID = "prebid"; @Mock private CurrencyConversionService currencyConversionService; @@ -55,9 +53,7 @@ public class KoblerBidderTest extends VertxTest { public void setUp() { target = new KoblerBidder( ENDPOINT_URL, - DEFAULT_BID_CURRENCY, DEV_ENDPOINT, - EXT_PREBID, currencyConversionService, jacksonMapper); } @@ -67,9 +63,7 @@ public void creationShouldFailOnInvalidEndpointUrl() { assertThatIllegalArgumentException().isThrownBy(() -> new KoblerBidder( "invalid_url", - DEFAULT_BID_CURRENCY, DEV_ENDPOINT, - EXT_PREBID, currencyConversionService, jacksonMapper)); } @@ -131,7 +125,7 @@ public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { // then assertThat(result.getValue()).hasSize(1); - assertThat(result.getValue().get(0).getUri()).isEqualTo(DEV_ENDPOINT); + assertThat(result.getValue().getFirst().getUri()).isEqualTo(DEV_ENDPOINT); } @Test @@ -145,7 +139,7 @@ public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getValue().get(0).getPayload().getCur()).containsExactlyInAnyOrder("EUR", "USD"); + assertThat(result.getValue().getFirst().getPayload().getCur()).containsExactlyInAnyOrder("EUR", "USD"); } @Test @@ -166,7 +160,7 @@ public void makeHttpRequestsShouldUseDevEndpointwhenImpExtTestIsTrue() { assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1); - final HttpRequest httpRequest = result.getValue().get(0); + final HttpRequest httpRequest = result.getValue().getFirst(); assertThat(httpRequest.getUri()).isEqualTo(DEV_ENDPOINT); } From 733c5bcd7b1b013190d99b3f738228f2955410af Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Wed, 2 Apr 2025 13:42:47 +0200 Subject: [PATCH 19/20] fix comments --- .../server/bidder/kobler/KoblerBidder.java | 50 +++-- .../bidder/kobler/KoblerBidderTest.java | 173 ++++++++++-------- 2 files changed, 122 insertions(+), 101 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 8bfa9211441..1595eee36e7 100644 --- a/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java +++ b/src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java @@ -54,6 +54,7 @@ public KoblerBidder(String endpointUrl, String devEndpoint, CurrencyConversionService currencyConversionService, JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(endpointUrl); this.devEndpoint = Objects.requireNonNull(devEndpoint); this.currencyConversionService = Objects.requireNonNull(currencyConversionService); @@ -63,18 +64,9 @@ public KoblerBidder(String endpointUrl, @Override public Result>> makeHttpRequests(BidRequest bidRequest) { final List errors = new ArrayList<>(); - boolean testMode = false; final List modifiedImps = new ArrayList<>(); - final List currencies = normalizeCurrencies(bidRequest); - final List imps = bidRequest.getImp(); - try { - testMode = isTest(imps.getFirst()); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); - } - for (Imp imp : imps) { try { modifiedImps.add(modifyImp(bidRequest, imp)); @@ -85,32 +77,16 @@ public Result>> makeHttpRequests(BidRequest bidRequ } final BidRequest modifiedRequest = bidRequest.toBuilder() - .cur(currencies) .imp(modifiedImps) + .cur(normalizeCurrencies(bidRequest)) .build(); - final String endpoint = testMode ? devEndpoint : endpointUrl; + final String endpoint = isTest(imps.getFirst(), errors) ? devEndpoint : endpointUrl; final HttpRequest httpRequest = BidderUtil.defaultRequest(modifiedRequest, endpoint, mapper); return Result.of(Collections.singletonList(httpRequest), errors); } - private boolean isTest(Imp imp) { - try { - return parseImpExt(imp).getTest(); - } catch (PreBidException e) { - return false; - } - } - - private List normalizeCurrencies(BidRequest bidRequest) { - final List currencies = new ArrayList<>(bidRequest.getCur()); - if (!currencies.contains(DEFAULT_BID_CURRENCY)) { - currencies.add(DEFAULT_BID_CURRENCY); - } - return currencies; - } - private Imp modifyImp(BidRequest bidRequest, Imp imp) { final Price resolvedBidFloor = resolveBidFloor(imp, bidRequest); @@ -137,6 +113,26 @@ private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) { return Price.of(DEFAULT_BID_CURRENCY, convertedPrice); } + private List normalizeCurrencies(BidRequest bidRequest) { + final List currencies = bidRequest.getCur(); + if (currencies.contains(DEFAULT_BID_CURRENCY)) { + return currencies; + } + + final List newCurrencies = new ArrayList<>(currencies); + newCurrencies.add(DEFAULT_BID_CURRENCY); + return newCurrencies; + } + + private boolean isTest(Imp imp, List errors) { + try { + return parseImpExt(imp).getTest(); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + return false; + } + } + private ExtImpKobler parseImpExt(Imp imp) { try { return mapper.mapper().convertValue(imp.getExt(), KOBLER_EXT_TYPE_REFERENCE).getBidder(); 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 66eeadcd9aa..467153dcd2f 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -1,7 +1,6 @@ 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; @@ -26,8 +25,8 @@ import org.prebid.server.proto.openrtb.ext.response.BidType; import java.math.BigDecimal; -import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.function.UnaryOperator; import static java.util.Collections.singletonList; @@ -42,8 +41,7 @@ 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"; - + private static final String DEV_ENDPOINT = "https://dev.test.com"; @Mock private CurrencyConversionService currencyConversionService; @@ -71,12 +69,8 @@ public void creationShouldFailOnInvalidEndpointUrl() { @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 = givenBidRequest(givenImp( + imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("EUR"))); when(currencyConversionService.convertCurrency(any(), any(), any(), any())) .thenThrow(new PreBidException("Currency conversion failed")); @@ -86,18 +80,15 @@ public void makeHttpRequestsShouldReturnErrorIfNoValidImps() { // then assertThat(result.getErrors()) - .hasSize(1) .extracting(BidderError::getMessage) - .containsExactlyInAnyOrder("Currency conversion failed"); + .containsExactly("Currency conversion failed"); } @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(givenImp( + imp -> imp.bidfloor(BigDecimal.ONE).bidfloorcur("EUR"))); when(currencyConversionService.convertCurrency(any(), any(), any(), any())).thenReturn(BigDecimal.TEN); @@ -116,62 +107,56 @@ public void makeHttpRequestsShouldConvertBidFloorCurrency() { @Test public void makeHttpRequestsShouldUseDevEndpointWhenTestModeEnabled() { // given - final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder - .cur(singletonList("EUR")), - imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true))))); + final BidRequest bidRequest = givenBidRequest(givenImp( + imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(true)))))); // when final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getValue()).hasSize(1); - assertThat(result.getValue().getFirst().getUri()).isEqualTo(DEV_ENDPOINT); + assertThat(result.getValue()) + .extracting(HttpRequest::getUri) + .containsExactly(DEV_ENDPOINT); } @Test - public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { + public void makeHttpRequestsShouldUseDefaultEndpointWhenTestModeDisabled() { // 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(givenImp(identity())); // when final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getValue().getFirst().getPayload().getCur()).containsExactlyInAnyOrder("EUR", "USD"); + assertThat(result.getValue()) + .extracting(HttpRequest::getUri) + .containsExactly(ENDPOINT_URL); } @Test - public void makeHttpRequestsShouldUseDevEndpointwhenImpExtTestIsTrue() { + public void makeHttpRequestsShouldAddUsdToCurrenciesIfMissing() { // given - final ObjectNode extNode = jacksonMapper.mapper().createObjectNode(); - extNode.putObject("bidder").put("test", true); - - 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 = givenBidRequest( + request -> request.cur(singletonList("EUR")), + givenImp(identity())); // when final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); - - final HttpRequest httpRequest = result.getValue().getFirst(); - assertThat(httpRequest.getUri()).isEqualTo(DEV_ENDPOINT); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getCur) + .containsExactly(List.of("EUR", "USD")); } @Test public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { // given - final BidderCall httpCall = givenHttpCall(); + final BidderCall httpCall = givenHttpCallWithBody("invalid"); // when - final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); - + final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).hasSize(1).allSatisfy(error -> { assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); @@ -180,80 +165,120 @@ public void makeBidsShouldReturnErrorIfResponseBodyIsInvalid() { } @Test - public void makeBidsShouldReturnBidsWithCorrectTypes() throws JsonProcessingException { + public void makeBidsShouldReturnEmptyListWhenBidResponseIsNull() { // given - final ObjectNode bidExt = mapper.createObjectNode() - .set("prebid", mapper.createObjectNode().put("type", "banner")); + final BidderCall httpCall = givenHttpCallWithBody("null"); - 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, null); + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListWhenSeatbidIsEmpty() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(givenBidResponse()); + + // when + final Result> result = target.makeBids(httpCall, null); + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnBidWithBannerType() throws JsonProcessingException { + // given + final Bid bid = Bid.builder() + .ext(mapper.valueToTree(Map.of("prebid", Map.of("type", "banner")))) + .build(); + + final BidderCall httpCall = givenHttpCall(givenBidResponse(bid)); // 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 - public void makeBidsShouldReturnEmptyListwhenBidResponseIsNull() { + public void makeBidsShouldReturnBannerWhenTypeIsNullOrMissing() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCallWithBody("null"); + final Bid bid = Bid.builder() + .ext(mapper.valueToTree(Map.of("prebid", Map.of("type", "null")))) + .build(); + + final BidderCall httpCall = givenHttpCall(givenBidResponse(bid)); // when final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); // then - assertThat(result.getValue()).isEmpty(); assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(BidType.banner); } @Test - public void makeBidsShouldReturnEmptyListwhenSeatbidIsEmpty() throws JsonProcessingException { + public void makeBidsShouldDefaultToBannerWhenPrebidTypeIsMissing() throws JsonProcessingException { // given - final BidResponse bidResponse = BidResponse.builder() - .seatbid(Collections.emptyList()) + final Bid bid = Bid.builder() + .ext(mapper.valueToTree(Map.of("prebid", Map.of()))) .build(); - final BidderCall httpCall = givenHttpCall(bidResponse); + + final BidderCall httpCall = givenHttpCall(givenBidResponse(bid)); // when final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); // then - assertThat(result.getValue()).isEmpty(); assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(BidType.banner); } - private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { - return givenBidRequest(identity(), impCustomizer); + private static BidRequest givenBidRequest(Imp... imps) { + return givenBidRequest(identity(), imps); } private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, - UnaryOperator impCustomizer) { - return bidRequestCustomizer.apply(BidRequest.builder() - .imp(singletonList(impCustomizer.apply(Imp.builder().id("123")).build()))).build(); - } + Imp... imps) { - private BidderCall givenHttpCall(BidResponse bidResponse) throws JsonProcessingException { - return BidderCall.succeededHttp( - HttpRequest.builder().payload(null).build(), - HttpResponse.of(200, null, mapper.writeValueAsString(bidResponse)), null); + return bidRequestCustomizer.apply(BidRequest.builder() + .cur(singletonList("USD")) + .imp(List.of(imps))) + .build(); } - private BidderCall givenHttpCall() { - return BidderCall.succeededHttp( - HttpRequest.builder().payload(null).build(), - HttpResponse.of(200, null, "invalid"), null); + private static Imp givenImp(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder() + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpKobler.of(false))))) + .build(); } - private BidderCall givenHttpCallWithBody(String body) { + private static BidderCall givenHttpCallWithBody(String body) { return BidderCall.succeededHttp( HttpRequest.builder().payload(null).build(), HttpResponse.of(200, null, body), null); } + + private static BidderCall givenHttpCall(BidResponse bidResponse) throws JsonProcessingException { + return givenHttpCallWithBody(mapper.writeValueAsString(bidResponse)); + } + + private static BidResponse givenBidResponse(Bid... bids) { + return BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(List.of(bids)).build())) + .build(); + } } From 0bcb6cf7d591dbf9d83462850bb7f8e350b22db3 Mon Sep 17 00:00:00 2001 From: pkaczmarek Date: Wed, 2 Apr 2025 16:40:23 +0200 Subject: [PATCH 20/20] fix comments --- .../org/prebid/server/bidder/kobler/KoblerBidderTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 467153dcd2f..7864ea726b1 100644 --- a/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/kobler/KoblerBidderTest.java @@ -198,7 +198,7 @@ public void makeBidsShouldReturnBidWithBannerType() throws JsonProcessingExcepti final BidderCall httpCall = givenHttpCall(givenBidResponse(bid)); // when - final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); + final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); @@ -217,7 +217,7 @@ public void makeBidsShouldReturnBannerWhenTypeIsNullOrMissing() throws JsonProce final BidderCall httpCall = givenHttpCall(givenBidResponse(bid)); // when - final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); + final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty(); @@ -236,7 +236,7 @@ public void makeBidsShouldDefaultToBannerWhenPrebidTypeIsMissing() throws JsonPr final BidderCall httpCall = givenHttpCall(givenBidResponse(bid)); // when - final Result> result = target.makeBids(httpCall, BidRequest.builder().build()); + final Result> result = target.makeBids(httpCall, null); // then assertThat(result.getErrors()).isEmpty();