diff --git a/src/main/java/org/prebid/server/bidder/floxis/FloxisBidder.java b/src/main/java/org/prebid/server/bidder/floxis/FloxisBidder.java index e90d244338f..feacd7aa080 100644 --- a/src/main/java/org/prebid/server/bidder/floxis/FloxisBidder.java +++ b/src/main/java/org/prebid/server/bidder/floxis/FloxisBidder.java @@ -13,7 +13,9 @@ 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; @@ -23,6 +25,7 @@ import org.prebid.server.util.BidderUtil; import org.prebid.server.util.Uri; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -43,12 +46,18 @@ public class FloxisBidder implements Bidder { private static final String DEFAULT_REGION = "us-e"; private static final String DEFAULT_PARTNER = "floxis"; + private static final String BIDDER_CURRENCY = "USD"; private final Uri endpointUrl; + private final CurrencyConversionService currencyConversionService; private final JacksonMapper mapper; - public FloxisBidder(String endpointUrl, JacksonMapper mapper) { + public FloxisBidder(String endpointUrl, + CurrencyConversionService currencyConversionService, + JacksonMapper mapper) { + this.endpointUrl = Uri.of(endpointUrl); + this.currencyConversionService = Objects.requireNonNull(currencyConversionService); this.mapper = Objects.requireNonNull(mapper); } @@ -59,19 +68,22 @@ public Result>> makeHttpRequests(BidRequest request for (Imp imp : request.getImp()) { final ExtImpFloxis impExt; + final Imp modifiedImp; try { impExt = parseImpExt(imp); + modifiedImp = modifyImp(imp, request); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); continue; } - impsByUrl.computeIfAbsent(resolveUrl(impExt), key -> new ArrayList<>()).add(imp); + impsByUrl.computeIfAbsent(resolveUrl(impExt), key -> new ArrayList<>()).add(modifiedImp); } + final List currencies = resolveCurrencies(request.getCur()); final List> httpRequests = impsByUrl.entrySet().stream() .map(entry -> BidderUtil.defaultRequest( - request.toBuilder().imp(entry.getValue()).build(), + request.toBuilder().imp(entry.getValue()).cur(currencies).build(), entry.getKey(), mapper)) .toList(); @@ -97,6 +109,27 @@ private ExtImpFloxis parseImpExt(Imp imp) { return ExtImpFloxis.of(impExt.getSeat(), region, partner); } + private Imp modifyImp(Imp imp, BidRequest request) { + final Price bidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); + if (!BidderUtil.shouldConvertBidFloor(bidFloorPrice, BIDDER_CURRENCY)) { + return imp; + } + + final BigDecimal convertedPrice = currencyConversionService.convertCurrency( + bidFloorPrice.getValue(), request, bidFloorPrice.getCurrency(), BIDDER_CURRENCY); + + return imp.toBuilder() + .bidfloor(convertedPrice) + .bidfloorcur(BIDDER_CURRENCY) + .build(); + } + + private static List resolveCurrencies(List currencies) { + return CollectionUtils.isEmpty(currencies) || currencies.contains(BIDDER_CURRENCY) + ? currencies + : Collections.singletonList(BIDDER_CURRENCY); + } + private String resolveUrl(ExtImpFloxis impExt) { return endpointUrl .replaceMacro(HOST_MACRO, resolveBidHost(impExt)) diff --git a/src/main/java/org/prebid/server/spring/config/bidder/FloxisConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/FloxisConfiguration.java index 896ac465fbe..3217e4aa9d4 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/FloxisConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/FloxisConfiguration.java @@ -2,6 +2,7 @@ import org.prebid.server.bidder.BidderDeps; import org.prebid.server.bidder.floxis.FloxisBidder; +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; @@ -25,11 +26,12 @@ BidderConfigurationProperties configurationProperties() { @Bean BidderDeps floxisBidderDeps(BidderConfigurationProperties floxisConfigurationProperties, + CurrencyConversionService currencyConversionService, JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(floxisConfigurationProperties) - .bidderCreator(config -> new FloxisBidder(config.getEndpoint(), mapper)) + .bidderCreator(config -> new FloxisBidder(config.getEndpoint(), currencyConversionService, mapper)) .assemble(); } } diff --git a/src/test/java/org/prebid/server/bidder/floxis/FloxisBidderTest.java b/src/test/java/org/prebid/server/bidder/floxis/FloxisBidderTest.java index 879b1661b24..8120c968724 100644 --- a/src/test/java/org/prebid/server/bidder/floxis/FloxisBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/floxis/FloxisBidderTest.java @@ -12,7 +12,11 @@ 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; @@ -20,10 +24,12 @@ 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.floxis.ExtImpFloxis; import org.prebid.server.proto.openrtb.ext.response.BidType; +import java.math.BigDecimal; import java.util.List; import java.util.function.UnaryOperator; @@ -33,17 +39,29 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +@ExtendWith(MockitoExtension.class) public class FloxisBidderTest extends VertxTest { private static final String ENDPOINT_URL = "https://{Host}.floxis.tech/pbs?seat={SeatId}"; - private final FloxisBidder target = new FloxisBidder(ENDPOINT_URL, jacksonMapper); + @Mock + private CurrencyConversionService currencyConversionService; + + private FloxisBidder target; + + @BeforeEach + public void setUp() { + target = new FloxisBidder(ENDPOINT_URL, currencyConversionService, jacksonMapper); + } @Test public void creationShouldFailOnInvalidEndpointUrl() { assertThatIllegalArgumentException() - .isThrownBy(() -> new FloxisBidder("invalid_url", jacksonMapper)); + .isThrownBy(() -> new FloxisBidder("invalid_url", currencyConversionService, jacksonMapper)); } @Test @@ -158,7 +176,7 @@ public void makeHttpRequestsShouldUsePartnerPrefixedHostWhenPartnerProvided() { @Test public void makeHttpRequestsShouldLeaveEndpointUnchangedWhenItDeclaresNoMacros() { // given - final FloxisBidder bidder = new FloxisBidder("http://localhost:8090/floxis-exchange", jacksonMapper); + final FloxisBidder bidder = new FloxisBidder("http://localhost:8090/floxis-exchange", currencyConversionService, jacksonMapper); final BidRequest bidRequest = givenBidRequest(imp -> imp.ext(givenImpExt("abc", "eu"))); // when @@ -174,7 +192,7 @@ public void makeHttpRequestsShouldLeaveEndpointUnchangedWhenItDeclaresNoMacros() @Test public void makeHttpRequestsShouldIgnoreRegionAndPartnerWhenAliasEndpointDeclaresNoHostMacro() { // given - final FloxisBidder bidder = new FloxisBidder("https://hb.adapex.io/pbs?seat={SeatId}", jacksonMapper); + final FloxisBidder bidder = new FloxisBidder("https://hb.adapex.io/pbs?seat={SeatId}", currencyConversionService, jacksonMapper); final BidRequest bidRequest = givenBidRequest(imp -> imp.ext(givenImpExt("abc", "eu", "acme"))); // when @@ -190,7 +208,7 @@ public void makeHttpRequestsShouldIgnoreRegionAndPartnerWhenAliasEndpointDeclare @Test public void makeHttpRequestsShouldRouteOnceWhenImpsDifferOnlyInRegionAndPartnerOnAliasEndpoint() { // given - final FloxisBidder bidder = new FloxisBidder("https://hb.adapex.io/pbs?seat={SeatId}", jacksonMapper); + final FloxisBidder bidder = new FloxisBidder("https://hb.adapex.io/pbs?seat={SeatId}", currencyConversionService, jacksonMapper); final BidRequest bidRequest = BidRequest.builder() .id("req-1") .imp(asList( @@ -405,6 +423,36 @@ public void makeHttpRequestsShouldForwardRequestBodyUnchanged() { .containsExactly(bidRequest); } + @Test + public void makeHttpRequestsShouldConvertBidFloorAndRequestCurrencyToUsd() { + // given + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willReturn(BigDecimal.TEN); + + final BidRequest bidRequest = givenBidRequest(imp -> imp + .bidfloor(BigDecimal.ONE) + .bidfloorcur("EUR") + .ext(givenImpExt("abc", "us-e"))) + .toBuilder() + .cur(singletonList("EUR")) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getCur) + .containsExactly(singletonList("USD")); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsExactly(tuple(BigDecimal.TEN, "USD")); + } + @Test public void makeBidsShouldReturnErrorWhenResponseBodyCouldNotBeParsed() { // given