Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adyoulike: add currency conversion #1791

Merged
merged 2 commits into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,25 @@
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpCall;
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.adyoulike.ExtImpAdyoulike;
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;
Expand All @@ -34,12 +39,18 @@ public class AdyoulikeBidder implements Bidder<BidRequest> {
private static final TypeReference<ExtPrebid<?, ExtImpAdyoulike>> ADYOULIKE_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};
private static final String BIDDER_CURRENCY = "USD";

private final String endpointUrl;
private final JacksonMapper mapper;
private final CurrencyConversionService currencyConversionService;

public AdyoulikeBidder(String endpointUrl,
CurrencyConversionService currencyConversionService,
JacksonMapper mapper) {
marki1an marked this conversation as resolved.
Show resolved Hide resolved

public AdyoulikeBidder(String endpointUrl, JacksonMapper mapper) {
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
this.mapper = Objects.requireNonNull(mapper);
}

Expand All @@ -52,8 +63,9 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request
for (Imp imp : request.getImp()) {
try {
final ExtImpAdyoulike impExt = parseImpExt(imp);
final Price bidFloorPrice = resolveBidFloor(imp, request);

modifiedImps.add(imp.toBuilder().tagid(impExt.getPlacement()).build());
modifiedImps.add(modifyImp(imp, impExt, bidFloorPrice));
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}
Expand All @@ -63,7 +75,10 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request
return Result.withErrors(errors);
}

final BidRequest outgoingRequest = request.toBuilder().imp(modifiedImps).build();
final BidRequest outgoingRequest = request.toBuilder()
.cur(Collections.singletonList(BIDDER_CURRENCY))
.imp(modifiedImps)
.build();

return Result.withValue(HttpRequest.<BidRequest>builder()
.method(HttpMethod.POST)
Expand All @@ -74,6 +89,36 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request
.build());
}

private static Imp modifyImp(Imp imp, ExtImpAdyoulike impExt, Price bidFloorPrice) {
return imp.toBuilder()
.bidfloorcur(bidFloorPrice.getCurrency())
.bidfloor(bidFloorPrice.getValue())
.tagid(impExt.getPlacement())
.build();
}

private Price resolveBidFloor(Imp imp, BidRequest bidRequest) {
final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
return BidderUtil.isValidPrice(initialBidFloorPrice)
&& !StringUtils.equalsIgnoreCase(initialBidFloorPrice.getCurrency(), BIDDER_CURRENCY)
? convertBidFloor(initialBidFloorPrice, imp.getId(), bidRequest)
: initialBidFloorPrice;
}

private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest) {
final String bidFloorCur = bidFloorPrice.getCurrency();
try {
final BigDecimal convertedPrice = currencyConversionService
.convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, BIDDER_CURRENCY);

return Price.of(BIDDER_CURRENCY, convertedPrice);
} catch (PreBidException e) {
throw new PreBidException(String.format(
"Unable to convert provided bid floor currency from %s to %s for imp `%s`",
bidFloorCur, BIDDER_CURRENCY, impId));
}
}

private ExtImpAdyoulike parseImpExt(Imp imp) {
try {
return mapper.mapper().convertValue(imp.getExt(), ADYOULIKE_EXT_TYPE_REFERENCE).getBidder();
Expand All @@ -91,7 +136,7 @@ private static MultiMap resolveHeaders() {
public final Result<List<BidderBid>> makeBids(HttpCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
return Result.of(extractBids(httpCall.getRequest().getPayload(), bidResponse), Collections.emptyList());
return Result.withValues(extractBids(httpCall.getRequest().getPayload(), bidResponse));
} catch (DecodeException | PreBidException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
Expand All @@ -110,7 +155,7 @@ private static List<BidderBid> bidsFromResponse(BidRequest bidRequest, BidRespon
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.map(bid -> BidderBid.of(bid, getBidType(bid.getImpid(), bidRequest.getImp()), bidResponse.getCur()))
.map(bid -> BidderBid.of(bid, getBidType(bid.getImpid(), bidRequest.getImp()), BIDDER_CURRENCY))
.collect(Collectors.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.adyoulike.AdyoulikeBidder;
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;
Expand Down Expand Up @@ -30,12 +31,15 @@ BidderConfigurationProperties configurationProperties() {
@Bean
BidderDeps adyoulikeBidderDeps(BidderConfigurationProperties adyoulikeConfigurationProperties,
@NotBlank @Value("${external-url}") String externalUrl,
CurrencyConversionService currencyConversionService,
JacksonMapper mapper) {

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(adyoulikeConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new AdyoulikeBidder(config.getEndpoint(), mapper))
.bidderCreator(config -> new AdyoulikeBidder(config.getEndpoint(),
currencyConversionService,
mapper))
.assemble();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,24 @@
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.prebid.server.VertxTest;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpCall;
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.adyoulike.ExtImpAdyoulike;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
Expand All @@ -30,6 +37,10 @@
import static java.util.function.Function.identity;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.prebid.server.proto.openrtb.ext.response.BidType.banner;
import static org.prebid.server.proto.openrtb.ext.response.BidType.video;
import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative;
Expand All @@ -38,16 +49,24 @@ public class AdyoulikeBidderTest extends VertxTest {

public static final String ENDPOINT_URL = "https://test.endpoint.com";

@Rule
public final MockitoRule mockitoRule = MockitoJUnit.rule();

private AdyoulikeBidder adyoulikeBidder;

@Mock
private CurrencyConversionService currencyConversionService;

@Before
public void setUp() {
adyoulikeBidder = new AdyoulikeBidder(ENDPOINT_URL, jacksonMapper);
adyoulikeBidder = new AdyoulikeBidder(ENDPOINT_URL, currencyConversionService, jacksonMapper);
}

@Test
public void creationShouldFailOnInvalidEndpointUrl() {
assertThatIllegalArgumentException().isThrownBy(() -> new AdyoulikeBidder("invalid_url", jacksonMapper));
assertThatIllegalArgumentException().isThrownBy(() -> new AdyoulikeBidder("invalid_url",
currencyConversionService,
jacksonMapper));
}

@Test
Expand All @@ -70,6 +89,50 @@ public void makeHttpRequestsShouldMakeOneRequestWithAllImps() {
.hasSize(2);
}

@Test
public void makeHttpRequestsShouldConvertCurrency() {
// given
given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString()))
.willReturn(BigDecimal.TEN);

final BidRequest bidRequest = givenBidRequest(imp -> imp
.bidfloor(BigDecimal.TEN)
.bidfloorcur("EUR"));

// when
final Result<List<HttpRequest<BidRequest>>> result = adyoulikeBidder.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 makeHttpRequestsShouldReturnErrorMessageOnFailedConvertCurrency() {
// given
given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString()))
.willThrow(PreBidException.class);

final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder
.bidfloor(BigDecimal.TEN)
.bidfloorcur("EUR"));

// when
final Result<List<HttpRequest<BidRequest>>> result = adyoulikeBidder.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).allSatisfy(bidderError -> {
assertThat(bidderError.getType())
.isEqualTo(BidderError.Type.bad_input);
assertThat(bidderError.getMessage())
.isEqualTo("Unable to convert provided bid floor currency from EUR to USD for imp `123`");
});
}

@Test
public void makeHttpRequestsShouldReturnErrorIfImpExtCanNotBeParsed() {
// given
Expand Down Expand Up @@ -188,7 +251,7 @@ public void makeBidsShouldReturnBannerBidByDefault() throws JsonProcessingExcept
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), banner, "EUR"));
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD"));
}

@Test
Expand All @@ -206,7 +269,7 @@ public void makeBidsShouldReturnBannerBidIfBannerIsPresent() throws JsonProcessi
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), banner, "EUR"));
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD"));
}

@Test
Expand All @@ -224,7 +287,7 @@ public void makeBidsShouldReturnVideoBidIfNoBannerAndHasVideo() throws JsonProce
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), video, "EUR"));
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), video, "USD"));
}

@Test
Expand All @@ -242,7 +305,7 @@ public void makeBidsShouldReturnNativeBidIfNoBannerAndHasNative() throws JsonPro
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), xNative, "EUR"));
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), xNative, "USD"));
}

@Test
Expand All @@ -261,14 +324,14 @@ public void makeBidsShouldReturnNativeBidIfNativeIsPresentInRequestImp() throws
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), xNative, "EUR"));
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), xNative, "USD"));
}

private static BidRequest givenBidRequest(
Function<Imp.ImpBuilder, Imp.ImpBuilder> impCustomizer,
Function<BidRequest.BidRequestBuilder, BidRequest.BidRequestBuilder> requestCustomizer) {
return requestCustomizer.apply(BidRequest.builder()
.imp(singletonList(givenImp(impCustomizer))))
.imp(singletonList(givenImp(impCustomizer))))
.build();
}

Expand All @@ -279,7 +342,7 @@ private static BidRequest givenBidRequest(

private static Imp givenImp(Function<Imp.ImpBuilder, Imp.ImpBuilder> impCustomizer) {
return impCustomizer.apply(Imp.builder()
.id("123"))
.id("123"))
.banner(Banner.builder().build())
.ext(mapper.valueToTree(ExtPrebid.of(null,
ExtImpAdyoulike.of("placementId", "campaign", "track",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"prebid": {
"type": "banner"
},
"origbidcpm": 3.33
"origbidcpm": 3.33,
"origbidcur" : "USD"
}
}
],
Expand Down