From f88ce6f7a4b963cacea70cdd25613705196bd87c Mon Sep 17 00:00:00 2001 From: antonbabak Date: Wed, 21 May 2025 12:58:51 +0200 Subject: [PATCH 1/2] Missena Adapter: Add formats and settings params --- .../bidder/missena/MissenaAdRequest.java | 48 +++- .../server/bidder/missena/MissenaBidder.java | 133 +++++++-- .../bidder/missena/MissenaUserParams.java | 24 ++ .../ext/request/missena/ExtImpMissena.java | 11 +- .../config/bidder/MissenaConfiguration.java | 7 +- src/main/resources/bidder-config/missena.yaml | 2 +- .../static/bidder-params/missena.json | 11 + .../bidder/missena/MissenaBidderTest.java | 259 ++++++++++++------ .../org/prebid/server/it/MissenaTest.java | 2 - .../missena/test-missena-bid-request.json | 21 +- 10 files changed, 390 insertions(+), 128 deletions(-) create mode 100644 src/main/java/org/prebid/server/bidder/missena/MissenaUserParams.java diff --git a/src/main/java/org/prebid/server/bidder/missena/MissenaAdRequest.java b/src/main/java/org/prebid/server/bidder/missena/MissenaAdRequest.java index 80bcba81fc6..68c0d862681 100644 --- a/src/main/java/org/prebid/server/bidder/missena/MissenaAdRequest.java +++ b/src/main/java/org/prebid/server/bidder/missena/MissenaAdRequest.java @@ -1,25 +1,59 @@ package org.prebid.server.bidder.missena; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.iab.openrtb.request.Eid; +import com.iab.openrtb.request.SupplyChain; import lombok.Builder; import lombok.Value; -@Builder(toBuilder = true) +import java.math.BigDecimal; +import java.util.List; + @Value +@Builder(toBuilder = true) public class MissenaAdRequest { - String requestId; + @JsonProperty("adunit") + String adUnit; + + @JsonProperty("buyeruid") + String buyerUid; + + Integer coppa; + + String currency; + + @JsonProperty("userEids") + List userEids; - int timeout; + BigDecimal floor; + + String floorCurrency; + + @JsonProperty("consent_required") + Boolean gdpr; + + @JsonProperty("consent_string") + String gdprConsent; + + @JsonProperty("ik") + String idempotencyKey; String referer; String refererCanonical; - String consentString; + String requestId; + + SupplyChain schain; + + Long timeout; + + String url; - boolean consentRequired; + MissenaUserParams params; - String placement; + String usPrivacy; - String test; + String version; } diff --git a/src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java b/src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java index 913fd3b44b8..62f1131114f 100644 --- a/src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java +++ b/src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java @@ -6,6 +6,7 @@ import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Regs; import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.Source; import com.iab.openrtb.request.User; import com.iab.openrtb.response.Bid; import io.vertx.core.MultiMap; @@ -16,7 +17,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; @@ -25,8 +28,13 @@ import org.prebid.server.proto.openrtb.ext.request.ExtUser; import org.prebid.server.proto.openrtb.ext.request.missena.ExtImpMissena; import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; +import org.prebid.server.version.PrebidVersionProvider; +import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -37,31 +45,40 @@ public class MissenaBidder implements Bidder { private static final TypeReference> TYPE_REFERENCE = new TypeReference<>() { }; - private static final int AD_REQUEST_DEFAULT_TIMEOUT = 2000; + private static final String USD_CURRENCY = "USD"; + private static final String EUR_CURRENCY = "EUR"; + private static final String PUBLISHER_ID_MACRO = "{{PublisherID}}"; private final String endpointUrl; private final JacksonMapper mapper; + private final CurrencyConversionService currencyConversionService; + private final PrebidVersionProvider prebidVersionProvider; + + public MissenaBidder(String endpointUrl, + JacksonMapper mapper, + CurrencyConversionService currencyConversionService, + PrebidVersionProvider prebidVersionProvider) { - public MissenaBidder(String endpointUrl, JacksonMapper mapper) { this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); this.mapper = Objects.requireNonNull(mapper); + this.currencyConversionService = Objects.requireNonNull(currencyConversionService); + this.prebidVersionProvider = Objects.requireNonNull(prebidVersionProvider); } @Override public Result>> makeHttpRequests(BidRequest request) { - final List> requests = new ArrayList<>(); final List errors = new ArrayList<>(); for (Imp imp : request.getImp()) { try { final ExtImpMissena extImp = parseImpExt(imp); - requests.add(makeHttpRequest(request, imp.getId(), extImp)); + final HttpRequest httpRequest = makeHttpRequest(request, imp, extImp); + return Result.of(Collections.singletonList(httpRequest), errors); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); } } - - return Result.of(requests, errors); + return Result.withErrors(errors); } private ExtImpMissena parseImpExt(Imp imp) { @@ -72,30 +89,95 @@ private ExtImpMissena parseImpExt(Imp imp) { } } - private HttpRequest makeHttpRequest(BidRequest request, String impId, ExtImpMissena extImp) { + private HttpRequest makeHttpRequest(BidRequest request, Imp imp, ExtImpMissena extImp) { final Site site = request.getSite(); + final String pageUrl = site != null ? site.getPage() : null; + final User user = request.getUser(); + final Regs regs = request.getRegs(); + final Device device = request.getDevice(); + final Source source = request.getSource(); + + final String requestCurrency = resolveCurrency(request.getCur()); + final Price floorInfo = resolveBidFloor(imp, request, requestCurrency); + + final MissenaUserParams userParams = MissenaUserParams.builder() + .formats(extImp.getFormats()) + .placement(extImp.getPlacement()) + .testMode(extImp.getTestMode()) + .settings(extImp.getSettings()) + .build(); final MissenaAdRequest missenaAdRequest = MissenaAdRequest.builder() + .adUnit(imp.getId()) + .buyerUid(user != null ? user.getBuyeruid() : null) + .coppa(regs != null ? regs.getCoppa() : null) + .currency(requestCurrency) + .userEids(user != null ? user.getEids() : null) + .floor(floorInfo.getValue()) + .floorCurrency(floorInfo.getCurrency()) + .gdpr(isGdpr(regs)) + .gdprConsent(getUserConsent(user)) + .idempotencyKey(request.getId()) + .referer(pageUrl) + .refererCanonical(site != null ? site.getDomain() : null) .requestId(request.getId()) - .timeout(AD_REQUEST_DEFAULT_TIMEOUT) - .referer(site == null ? null : site.getPage()) - .refererCanonical(site == null ? null : site.getDomain()) - .consentString(getUserConsent(request.getUser())) - .consentRequired(isGdpr(request.getRegs())) - .placement(extImp.getPlacement()) - .test(extImp.getTestMode()) + .schain(source != null ? source.getSchain() : null) + .timeout(request.getTmax()) + .params(userParams) + .version(prebidVersionProvider.getNameVersionRecord()) .build(); return HttpRequest.builder() .method(HttpMethod.POST) - .uri(makeUrl(extImp.getApiKey())) - .headers(makeHeaders(request.getDevice(), site)) - .impIds(Collections.singleton(impId)) + .uri(resolveEndpointUrl(extImp.getApiKey())) + .headers(makeHeaders(device, site)) + .impIds(Collections.singleton(imp.getId())) .body(mapper.encodeToBytes(missenaAdRequest)) .payload(missenaAdRequest) .build(); } + private Price resolveBidFloor(Imp imp, BidRequest bidRequest, String targetCurrency) { + final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); + return BidderUtil.isValidPrice(initialBidFloorPrice) + ? convertBidFloor(initialBidFloorPrice, imp.getId(), bidRequest, targetCurrency) + : initialBidFloorPrice; + } + + private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest, String targetCurrency) { + final String bidFloorCur = bidFloorPrice.getCurrency(); + + if (targetCurrency.equalsIgnoreCase(bidFloorCur)) { + return bidFloorPrice; + } + + try { + final BigDecimal convertedPrice = currencyConversionService + .convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, targetCurrency); + + return Price.of(targetCurrency, convertedPrice); + } catch (PreBidException e) { + throw new PreBidException("Unable to convert provided bid floor currency from %s to %s for imp `%s`" + .formatted(bidFloorCur, targetCurrency, impId)); + } + } + + private String resolveCurrency(List requestCurrencies) { + for (String currency : requestCurrencies) { + if (USD_CURRENCY.equalsIgnoreCase(currency)) { + return USD_CURRENCY; + } + } + + for (String currency : requestCurrencies) { + if (EUR_CURRENCY.equalsIgnoreCase(currency)) { + return EUR_CURRENCY; + } + } + + return USD_CURRENCY; + } + private MultiMap makeHeaders(Device device, Site site) { final MultiMap headers = HttpUtil.headers(); @@ -105,15 +187,21 @@ private MultiMap makeHeaders(Device device, Site site) { HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6()); } - if (site != null) { + if (site != null && StringUtils.isNotBlank(site.getPage())) { HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, site.getPage()); + try { + final URL url = new URL(site.getPage()); + final String origin = url.getProtocol() + "://" + url.getHost(); + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.ORIGIN_HEADER, origin); + } catch (MalformedURLException e) { + // do nothing + } } - return headers; } - private String makeUrl(String apiKey) { - return endpointUrl + "?t=%s".formatted(apiKey); + private String resolveEndpointUrl(String apiKey) { + return endpointUrl.replace(PUBLISHER_ID_MACRO, HttpUtil.encodeUrl(apiKey)); } private static boolean isGdpr(Regs regs) { @@ -128,7 +216,8 @@ private static String getUserConsent(User user) { return Optional.ofNullable(user) .map(User::getExt) .map(ExtUser::getConsent) - .orElse(StringUtils.EMPTY); + .filter(StringUtils::isNotBlank) + .orElse(null); } @Override diff --git a/src/main/java/org/prebid/server/bidder/missena/MissenaUserParams.java b/src/main/java/org/prebid/server/bidder/missena/MissenaUserParams.java new file mode 100644 index 00000000000..3d978a7937f --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/missena/MissenaUserParams.java @@ -0,0 +1,24 @@ +package org.prebid.server.bidder.missena; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.node.ObjectNode; // Changed import +import lombok.Builder; +import lombok.Value; + +import java.util.List; + +@Builder +@Value +public class MissenaUserParams { + + List formats; + + String placement; + + @JsonProperty("test") + String testMode; + + ObjectNode settings; + +} + diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/missena/ExtImpMissena.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/missena/ExtImpMissena.java index 016e6ca99d5..0c3a8fd08ab 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/missena/ExtImpMissena.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/missena/ExtImpMissena.java @@ -1,16 +1,25 @@ package org.prebid.server.proto.openrtb.ext.request.missena; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Builder; import lombok.Value; -@Value(staticConstructor = "of") +import java.util.List; + +@Value +@Builder(toBuilder = true) public class ExtImpMissena { @JsonProperty("apiKey") String apiKey; + List formats; + String placement; @JsonProperty("test") String testMode; + + ObjectNode settings; } diff --git a/src/main/java/org/prebid/server/spring/config/bidder/MissenaConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/MissenaConfiguration.java index 1c9c7fd355f..798f57dc35e 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/MissenaConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/MissenaConfiguration.java @@ -2,11 +2,13 @@ import org.prebid.server.bidder.BidderDeps; import org.prebid.server.bidder.missena.MissenaBidder; +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.prebid.server.version.PrebidVersionProvider; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -30,12 +32,15 @@ BidderConfigurationProperties configurationProperties() { @Bean BidderDeps missenaBidderDeps(BidderConfigurationProperties missenaConfigurationProperties, @NotBlank @Value("${external-url}") String externalUrl, + CurrencyConversionService currencyConversionService, + PrebidVersionProvider prebidVersionProvider, JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(missenaConfigurationProperties) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new MissenaBidder(config.getEndpoint(), mapper)) + .bidderCreator(config -> new MissenaBidder( + config.getEndpoint(), mapper, currencyConversionService, prebidVersionProvider)) .assemble(); } } diff --git a/src/main/resources/bidder-config/missena.yaml b/src/main/resources/bidder-config/missena.yaml index 4f1ea9e4f8f..f8921eac3db 100644 --- a/src/main/resources/bidder-config/missena.yaml +++ b/src/main/resources/bidder-config/missena.yaml @@ -1,6 +1,6 @@ adapters: missena: - endpoint: https://bid.missena.io/ + endpoint: https://bid.missena.io/?t={{PublisherID}} meta-info: maintainer-email: prebid@missena.com modifying-vast-xml-allowed: true diff --git a/src/main/resources/static/bidder-params/missena.json b/src/main/resources/static/bidder-params/missena.json index 86bf5b45dec..be5217efdb1 100644 --- a/src/main/resources/static/bidder-params/missena.json +++ b/src/main/resources/static/bidder-params/missena.json @@ -16,6 +16,17 @@ "test": { "type": "string", "description": "Test Mode" + }, + "formats": { + "type": "array", + "description": "An array of formats to request (banner, native, or video)", + "items": { + "type": "string" + } + }, + "settings": { + "type": "object", + "description": "An object containing extra settings for the Missena adapter" } }, "required": [ diff --git a/src/test/java/org/prebid/server/bidder/missena/MissenaBidderTest.java b/src/test/java/org/prebid/server/bidder/missena/MissenaBidderTest.java index 83326e4049f..453fcd26739 100644 --- a/src/test/java/org/prebid/server/bidder/missena/MissenaBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/missena/MissenaBidderTest.java @@ -7,9 +7,15 @@ import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Regs; import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.Source; +import com.iab.openrtb.request.SupplyChain; import com.iab.openrtb.request.User; import com.iab.openrtb.response.Bid; +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; @@ -17,20 +23,28 @@ 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.ExtRegs; import org.prebid.server.proto.openrtb.ext.request.ExtUser; import org.prebid.server.proto.openrtb.ext.request.missena.ExtImpMissena; import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.HttpUtil; +import org.prebid.server.version.PrebidVersionProvider; import java.math.BigDecimal; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.function.UnaryOperator; -import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mock.Strictness.LENIENT; import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER; import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE; import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER; @@ -39,17 +53,37 @@ import static org.prebid.server.util.HttpUtil.X_FORWARDED_FOR_HEADER; import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; +@ExtendWith(MockitoExtension.class) class MissenaBidderTest extends VertxTest { - private static final String ENDPOINT_URL = "https://test-url.com"; + private static final String ENDPOINT_URL = "https://test-url.com/?t={{PublisherID}}"; + private static final String TEST_PBS_VERSION = "pbs-java/1.0"; - private final MissenaBidder target = new MissenaBidder(ENDPOINT_URL, jacksonMapper); + @Mock(strictness = LENIENT) + private CurrencyConversionService currencyConversionService; + + @Mock(strictness = LENIENT) + private PrebidVersionProvider prebidVersionProvider; + + private MissenaBidder target; + + @BeforeEach + public void setUp() { + target = new MissenaBidder( + ENDPOINT_URL, + jacksonMapper, + currencyConversionService, + prebidVersionProvider); + + given(prebidVersionProvider.getNameVersionRecord()).willReturn(TEST_PBS_VERSION); + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willAnswer(invocation -> invocation.getArgument(0)); + } @Test public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given - final BidRequest bidRequest = givenBidRequest( - imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + final BidRequest bidRequest = givenBidRequest(imp -> imp.ext(givenInvalidImpExt())); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -61,59 +95,90 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { } @Test - public void makeHttpRequestsShouldMakeOneRequestPerImp() { + public void makeHttpRequestsShouldMakeRequestForFirstValidImp() { // given - final BidRequest bidRequest = givenBidRequest( - imp -> imp.id("givenImp1").ext(givenImpExt("apiKey1", "plId1", "test1")), - imp -> imp.id("givenImp2").ext(givenImpExt("apiKey2", "plId2", "test2"))) - .toBuilder() + final ObjectNode settingsNode = mapper.createObjectNode().put("settingKey", "settingValue"); + + final BidRequest bidRequest = BidRequest.builder() .id("requestId") - .site(Site.builder().page("page").domain("domain").build()) + .tmax(500L) + .cur(singletonList("USD")) + .imp(List.of( + givenImp(imp -> imp.id("impId1") + .ext(givenImpExt("apiKey1", "placementId1", "1", List.of("banner"), settingsNode))), + givenImp(imp -> imp.id("impId2") + .ext(givenImpExt("apiKey2", "placementId2", "0", null, null))))) + .site(Site.builder().page("http://test.com/page").domain("test.com").build()) .regs(Regs.builder().ext(ExtRegs.of(1, null, null, null)).build()) - .user(User.builder().ext(ExtUser.builder().consent("consent").build()).build()) + .user(User.builder().buyeruid("buyer1") + .ext(ExtUser.builder().consent("consentStr").build()).build()) + .source(Source.builder().schain(SupplyChain.of(1, null, null, null)).build()) + .device(Device.builder().ua("test-ua").ip("123.123.123.123").build()) .build(); - //when + // when final Result>> result = target.makeHttpRequests(bidRequest); - //then - final MissenaAdRequest expectedRequest = MissenaAdRequest.builder() + // then + final MissenaUserParams expectedUserParams = MissenaUserParams.builder() + .formats(List.of("banner")) + .placement("placementId1") + .testMode("1") + .settings(settingsNode) + .build(); + + final MissenaAdRequest expectedPayload = MissenaAdRequest.builder() + .adUnit("impId1") + .buyerUid("buyer1") + .coppa(null) + .currency("USD") + .userEids(null) + .floor(BigDecimal.valueOf(0.1)) + .floorCurrency("USD") + .gdpr(true) + .gdprConsent("consentStr") + .idempotencyKey("requestId") + .referer("http://test.com/page") + .refererCanonical("test.com") .requestId("requestId") - .timeout(2000) - .referer("page") - .refererCanonical("domain") - .consentString("consent") - .consentRequired(true) + .schain(SupplyChain.of(1, null, null, null)) + .timeout(500L) + .params(expectedUserParams) + .version(TEST_PBS_VERSION) .build(); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(2) + assertThat(result.getValue()).hasSize(1) .extracting(HttpRequest::getPayload) - .containsExactlyInAnyOrder( - expectedRequest.toBuilder().placement("plId1").test("test1").build(), - expectedRequest.toBuilder().placement("plId2").test("test2").build()); + .containsExactly(expectedPayload); + assertThat(result.getValue()) + .extracting(HttpRequest::getImpIds) + .containsExactly(Collections.singleton("impId1")); } @Test - public void makeHttpRequestsShouldHaveImpIds() { + public void makeHttpRequestsShouldReturnErrorIfAllImpsAreInvalid() { // given - final BidRequest bidRequest = givenBidRequest(imp -> imp.id("givenImp1"), imp -> imp.id("givenImp2")); - //when + final BidRequest bidRequest = givenBidRequest( + imp -> imp.ext(givenInvalidImpExt()), + imp -> imp.ext(givenInvalidImpExt())); + + // when final Result>> result = target.makeHttpRequests(bidRequest); - //then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(2) - .extracting(HttpRequest::getImpIds) - .containsExactlyInAnyOrder(singleton("givenImp1"), singleton("givenImp2")); + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(2) + .extracting(BidderError::getMessage) + .containsOnly("Error parsing missenaExt parameters"); } @Test - public void makeHttpRequestsShouldReturnExpectedHeadersWhenDeviceHasIp() { + public void makeHttpRequestsShouldReturnExpectedHeadersWhenDeviceHasIpAndIpv6() { // given final BidRequest bidRequest = givenBidRequest(identity()) .toBuilder() - .site(Site.builder().page("page").build()) + .site(Site.builder().page("http://page.com").build()) .device(Device.builder().ua("ua").ip("ip").ipv6("ipv6").build()) .build(); @@ -121,19 +186,17 @@ public void makeHttpRequestsShouldReturnExpectedHeadersWhenDeviceHasIp() { final Result>> result = target.makeHttpRequests(bidRequest); // then + assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1).first() .extracting(HttpRequest::getHeaders) - .satisfies(headers -> assertThat(headers.get(CONTENT_TYPE_HEADER)) - .isEqualTo(APPLICATION_JSON_CONTENT_TYPE)) - .satisfies(headers -> assertThat(headers.get(ACCEPT_HEADER)) - .isEqualTo(APPLICATION_JSON_VALUE)) - .satisfies(headers -> assertThat(headers.get(USER_AGENT_HEADER)) - .isEqualTo("ua")) - .satisfies(headers -> assertThat(headers.get(X_FORWARDED_FOR_HEADER)) - .isEqualTo("ip")) - .satisfies(headers -> assertThat(headers.get(REFERER_HEADER)) - .isEqualTo("page")); - assertThat(result.getErrors()).isEmpty(); + .satisfies(headers -> { + assertThat(headers.get(CONTENT_TYPE_HEADER)).isEqualTo(APPLICATION_JSON_CONTENT_TYPE); + assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE); + assertThat(headers.get(USER_AGENT_HEADER)).isEqualTo("ua"); + assertThat(headers.getAll(X_FORWARDED_FOR_HEADER)).containsExactlyInAnyOrder("ip", "ipv6"); + assertThat(headers.get(REFERER_HEADER)).isEqualTo("http://page.com"); + assertThat(headers.get(HttpUtil.ORIGIN_HEADER)).isEqualTo("http://page.com"); + }); } @Test @@ -148,45 +211,47 @@ public void makeHttpRequestsShouldReturnExpectedHeadersWhenDeviceHasIpv6Only() { final Result>> result = target.makeHttpRequests(bidRequest); // then + assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1).first() .extracting(HttpRequest::getHeaders) - .satisfies(headers -> assertThat(headers.get(CONTENT_TYPE_HEADER)) - .isEqualTo(APPLICATION_JSON_CONTENT_TYPE)) - .satisfies(headers -> assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE)) - .satisfies(headers -> assertThat(headers.get(USER_AGENT_HEADER)).isNull()) - .satisfies(headers -> assertThat(headers.get(X_FORWARDED_FOR_HEADER)).isEqualTo("ipv6")) - .satisfies(headers -> assertThat(headers.get(REFERER_HEADER)).isNull()); - assertThat(result.getErrors()).isEmpty(); + .satisfies(headers -> { + assertThat(headers.get(CONTENT_TYPE_HEADER)).isEqualTo(APPLICATION_JSON_CONTENT_TYPE); + assertThat(headers.get(ACCEPT_HEADER)).isEqualTo(APPLICATION_JSON_VALUE); + assertThat(headers.get(USER_AGENT_HEADER)).isNull(); + assertThat(headers.getAll(X_FORWARDED_FOR_HEADER)).containsExactly("ipv6"); + assertThat(headers.get(REFERER_HEADER)).isNull(); + }); } @Test - public void shouldMakeOneRequestWhenOneImpIsValidAndAnotherAreInvalid() { + public void makeHttpRequestsShouldConvertBidFloorCurrency() { // given - final BidRequest bidRequest = givenBidRequest( - imp -> imp.id("impId1").ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))), - imp -> imp.id("impId2").ext(givenImpExt("apiKey", "placement", "testMode"))); + final Imp imp = givenImp(i -> i.bidfloor(BigDecimal.TEN).bidfloorcur("EUR") + .ext(givenImpExt("key1"))); + final BidRequest bidRequest = BidRequest.builder().id("reqId").tmax(1000L) + .imp(singletonList(imp)) + .cur(singletonList("USD")) + .build(); + + // Specific mock for this test, overrides the general one in setUp + given(currencyConversionService.convertCurrency(BigDecimal.TEN, bidRequest, "EUR", "USD")) + .willReturn(BigDecimal.valueOf(12)); - //when + // when final Result>> result = target.makeHttpRequests(bidRequest); // then - final MissenaAdRequest expectedRequest = MissenaAdRequest.builder() - .timeout(2000) - .consentString("") - .consentRequired(false) - .test("testMode") - .placement("placement") - .build(); - - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .containsOnly(expectedRequest); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue().getFirst().getPayload()) + .extracting(MissenaAdRequest::getFloor, MissenaAdRequest::getFloorCurrency) + .containsExactly(BigDecimal.valueOf(12), "USD"); } @Test public void makeHttpRequestsShouldUseCorrectUri() { // given - final BidRequest bidRequest = givenBidRequest(imp -> imp.ext(givenImpExt("apiKey", "plId", "test"))); + final BidRequest bidRequest = givenBidRequest(imp -> imp.ext(givenImpExt("testApiKey"))); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -195,15 +260,14 @@ public void makeHttpRequestsShouldUseCorrectUri() { assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) .extracting(HttpRequest::getUri) - .containsExactly("https://test-url.com?t=apiKey"); + .containsExactly("https://test-url.com/?t=testApiKey"); } @Test public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { // given final BidderCall httpCall = givenHttpCall("invalid"); - final BidRequest bidRequest = givenBidRequest(imp -> imp.id("impId1"), imp -> imp.id("impId2")) - .toBuilder().id("requestId").build(); + final BidRequest bidRequest = givenBidRequest(imp -> imp.id("impId1")); // when final Result> result = target.makeBids(httpCall, bidRequest); @@ -225,9 +289,9 @@ public void makeBidsShouldReturnSingleBid() throws JsonProcessingException { .currency("USD") .ad("adm") .build(); + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(bidResponse)); - final BidRequest bidRequest = givenBidRequest(imp -> imp.id("impId1"), imp -> imp.id("impId2")) - .toBuilder().id("requestId").build(); + final BidRequest bidRequest = givenBidRequest(imp -> imp.id("impId")).toBuilder().id("requestId").build(); // when final Result> result = target.makeBids(httpCall, bidRequest); @@ -235,33 +299,57 @@ public void makeBidsShouldReturnSingleBid() throws JsonProcessingException { // then assertThat(result.getErrors()).isEmpty(); - final Bid expetedBid = Bid.builder() - .adm("adm") + final Bid expectedBid = Bid.builder() + .id("requestId") + .impid("impId") .price(BigDecimal.TEN) + .adm("adm") .crid("id") - .impid("impId1") - .id("requestId") .build(); assertThat(result.getValue()).hasSize(1) - .containsOnly(BidderBid.of(expetedBid, BidType.banner, "USD")); + .containsOnly(BidderBid.of(expectedBid, BidType.banner, "USD")); } private static BidRequest givenBidRequest(UnaryOperator... impCustomizers) { - return BidRequest.builder() - .imp(Arrays.stream(impCustomizers).map(MissenaBidderTest::givenImp).toList()) - .build(); + final List imps = Arrays.stream(impCustomizers) + .map(MissenaBidderTest::givenImp) + .toList(); + return BidRequest.builder().imp(imps).cur(singletonList("USD")).build(); } private static Imp givenImp(UnaryOperator impCustomizer) { return impCustomizer.apply(Imp.builder() .id("impId") - .ext(givenImpExt("apikey", "placementId", "test"))) + .bidfloor(BigDecimal.valueOf(0.1)) + .bidfloorcur("USD") + .ext(givenImpExt("defaultApiKey"))) + .build(); + } + + private static ObjectNode givenImpExt(String apiKey) { + return givenImpExt(apiKey, null, null, null, null); + } + + private static ObjectNode givenImpExt(String apiKey, + String placement, + String testMode, + List formats, + ObjectNode settings) { + + final ExtImpMissena extImpMissena = ExtImpMissena.builder() + .apiKey(apiKey) + .placement(placement) + .testMode(testMode) + .formats(formats) + .settings(settings) .build(); + + return mapper.valueToTree(ExtPrebid.of(null, extImpMissena)); } - private static ObjectNode givenImpExt(String apiKey, String placement, String testMode) { - return mapper.valueToTree(ExtPrebid.of(null, ExtImpMissena.of(apiKey, placement, testMode))); + private static ObjectNode givenInvalidImpExt() { + return mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())); } private static BidderCall givenHttpCall(String body) { @@ -270,5 +358,4 @@ private static BidderCall givenHttpCall(String body) { HttpResponse.of(200, null, body), null); } - } diff --git a/src/test/java/org/prebid/server/it/MissenaTest.java b/src/test/java/org/prebid/server/it/MissenaTest.java index e86227fb358..abb37647910 100644 --- a/src/test/java/org/prebid/server/it/MissenaTest.java +++ b/src/test/java/org/prebid/server/it/MissenaTest.java @@ -8,7 +8,6 @@ import java.io.IOException; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; 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; @@ -20,7 +19,6 @@ public class MissenaTest extends IntegrationTest { public void openrtb2AuctionShouldRespondWithBidsFromMissena() throws IOException, JSONException { // given WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/missena-exchange")) - .withQueryParam("t", equalTo("apiKey")) .withRequestBody(equalToJson(jsonFrom("openrtb2/missena/test-missena-bid-request.json"))) .willReturn(aResponse().withBody(jsonFrom("openrtb2/missena/test-missena-bid-response.json")))); diff --git a/src/test/resources/org/prebid/server/it/openrtb2/missena/test-missena-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/missena/test-missena-bid-request.json index aa398c49b22..5d6eacd1e38 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/missena/test-missena-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/missena/test-missena-bid-request.json @@ -1,10 +1,15 @@ { - "request_id": "request_id", - "timeout": 2000, - "referer": "http://www.example.com", - "referer_canonical": "www.example.com", - "consent_string": "", - "consent_required": false, - "placement": "placement", - "test": "test" + "adunit" : "imp_id", + "currency" : "USD", + "consent_required" : false, + "ik" : "request_id", + "referer" : "http://www.example.com", + "referer_canonical" : "www.example.com", + "request_id" : "request_id", + "timeout" : "${json-unit.any-number}", + "params" : { + "placement" : "placement", + "test" : "test" + }, + "version" : "${json-unit.any-string}" } From f6f88bb4820a2fd2496ec130e2ab107f11d075d0 Mon Sep 17 00:00:00 2001 From: antonbabak Date: Thu, 29 May 2025 14:37:54 +0200 Subject: [PATCH 2/2] Fix comments --- .../server/bidder/missena/MissenaBidder.java | 21 +++++++------------ .../bidder/missena/MissenaUserParams.java | 1 - 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java b/src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java index 62f1131114f..4cad9af88f7 100644 --- a/src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java +++ b/src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java @@ -91,7 +91,6 @@ private ExtImpMissena parseImpExt(Imp imp) { private HttpRequest makeHttpRequest(BidRequest request, Imp imp, ExtImpMissena extImp) { final Site site = request.getSite(); - final String pageUrl = site != null ? site.getPage() : null; final User user = request.getUser(); final Regs regs = request.getRegs(); final Device device = request.getDevice(); @@ -118,7 +117,7 @@ private HttpRequest makeHttpRequest(BidRequest request, Imp im .gdpr(isGdpr(regs)) .gdprConsent(getUserConsent(user)) .idempotencyKey(request.getId()) - .referer(pageUrl) + .referer(site != null ? site.getPage() : null) .refererCanonical(site != null ? site.getDomain() : null) .requestId(request.getId()) .schain(source != null ? source.getSchain() : null) @@ -147,10 +146,6 @@ private Price resolveBidFloor(Imp imp, BidRequest bidRequest, String targetCurre private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest, String targetCurrency) { final String bidFloorCur = bidFloorPrice.getCurrency(); - if (targetCurrency.equalsIgnoreCase(bidFloorCur)) { - return bidFloorPrice; - } - try { final BigDecimal convertedPrice = currencyConversionService .convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, targetCurrency); @@ -163,19 +158,19 @@ private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidR } private String resolveCurrency(List requestCurrencies) { - for (String currency : requestCurrencies) { - if (USD_CURRENCY.equalsIgnoreCase(currency)) { + String currency = USD_CURRENCY; + + for (String requestCurrency : requestCurrencies) { + if (USD_CURRENCY.equalsIgnoreCase(requestCurrency)) { return USD_CURRENCY; } - } - for (String currency : requestCurrencies) { - if (EUR_CURRENCY.equalsIgnoreCase(currency)) { - return EUR_CURRENCY; + if (EUR_CURRENCY.equalsIgnoreCase(requestCurrency)) { + currency = EUR_CURRENCY; } } - return USD_CURRENCY; + return currency; } private MultiMap makeHeaders(Device device, Site site) { diff --git a/src/main/java/org/prebid/server/bidder/missena/MissenaUserParams.java b/src/main/java/org/prebid/server/bidder/missena/MissenaUserParams.java index 3d978a7937f..e63a704a60c 100644 --- a/src/main/java/org/prebid/server/bidder/missena/MissenaUserParams.java +++ b/src/main/java/org/prebid/server/bidder/missena/MissenaUserParams.java @@ -19,6 +19,5 @@ public class MissenaUserParams { String testMode; ObjectNode settings; - }