From 2e1b6a5eb4d6d6c0ad347f05b4e94461857a3d7a Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Fri, 5 Sep 2025 16:29:56 +0200 Subject: [PATCH 1/7] add showheroes bid adapter --- .../bidder/showheroes/ShowheroesBidder.java | 213 ++++++++++ .../request/showheroes/ExtImpShowheroes.java | 14 + .../bidder/ShowheroesConfiguration.java | 49 +++ .../resources/bidder-config/showheroes.yaml | 17 + .../static/bidder-params/showheroes.json | 13 + .../showheroes/ShowheroesBidderTest.java | 377 ++++++++++++++++++ .../org/prebid/server/it/ShowheroesTest.java | 40 ++ .../test-auction-showheroes-request.json | 24 ++ .../test-auction-showheroes-response.json | 40 ++ .../test-showheroes-bid-request.json | 57 +++ .../test-showheroes-bid-response.json | 23 ++ 11 files changed, 867 insertions(+) create mode 100644 src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java create mode 100644 src/main/resources/bidder-config/showheroes.yaml create mode 100644 src/main/resources/static/bidder-params/showheroes.json create mode 100644 src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java create mode 100644 src/test/java/org/prebid/server/it/ShowheroesTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java new file mode 100644 index 00000000000..f3450069acd --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java @@ -0,0 +1,213 @@ +package org.prebid.server.bidder.showheroes; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Video; +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.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.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.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidChannel; +import org.prebid.server.proto.openrtb.ext.request.showheroes.ExtImpShowheroes; +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.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class ShowheroesBidder implements Bidder { + + private static final String BID_CURRENCY = "EUR"; + private static final String DEFAULT_ORTB_CURRENCY = "USD"; + private static final TypeReference> SHOWHEROES_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; + + private final String endpointUrl; + private final CurrencyConversionService currencyConversionService; + private final JacksonMapper mapper; + + public ShowheroesBidder(String endpointUrl, + CurrencyConversionService currencyConversionService, + PrebidVersionProvider prebidVersionProvider, + JacksonMapper mapper) { + + this.endpointUrl = HttpUtil.validateUrlSyntax(Objects.requireNonNull(endpointUrl)); + this.currencyConversionService = Objects.requireNonNull(currencyConversionService); + this.mapper = Objects.requireNonNull(mapper); + } + + private BidderError validate(BidRequest bidRequest) { + // request must contain site object with page or app object with bundle + if (bidRequest.getSite() == null && bidRequest.getApp() == null) { + return BidderError.badInput("BidRequest must contain one of site or app"); + } + if (bidRequest.getSite() != null && bidRequest.getSite().getPage() == null) { + return BidderError.badInput("BidRequest.site.page is required"); + } + if (bidRequest.getApp() != null && bidRequest.getApp().getBundle() == null) { + return BidderError.badInput("BidRequest.app.bundle is required"); + } + return null; + } + + private ExtRequestPrebidChannel getPrebidChannel(BidRequest bidRequest) { + return Optional.ofNullable(bidRequest.getExt()) + .map(ExtRequest::getPrebid) + .map(ExtRequestPrebid::getChannel) + .orElse(null); + } + + private Imp processImpression(BidRequest bidRequest, Imp imp, ExtRequestPrebidChannel prebidChannel) { + final Banner banner = imp.getBanner(); + final Video video = imp.getVideo(); + if (banner == null && video == null) { + throw new PreBidException("Impression must contain one of banner, video or native"); + } + + final ExtImpShowheroes extImpShowheroes = mapper.mapper() + .convertValue(imp.getExt(), SHOWHEROES_EXT_TYPE_REFERENCE).getBidder(); + if (extImpShowheroes == null || extImpShowheroes.getUnitId() == null + || extImpShowheroes.getUnitId().isBlank()) { + throw new PreBidException("Ext.imp.bidder.unitId is required"); + } + + String channelName = null; + String channelVersion = null; + if (prebidChannel != null) { + channelName = prebidChannel.getName(); + channelVersion = prebidChannel.getVersion(); + } + + final ObjectNode impExt = imp.getExt(); + + final Imp.ImpBuilder impBuilder = imp.toBuilder(); + + // copy unitId from ext.bidder to ext.params + impExt.set("params", JsonNodeFactory.instance.objectNode() + .put("unitId", extImpShowheroes.getUnitId())); + + impBuilder.ext(impExt); + if (imp.getDisplaymanager() == null && channelName != null) { + impBuilder.displaymanager(channelName); + impBuilder.displaymanagerver(channelVersion); + } + + String currency = imp.getBidfloorcur(); + // if floor price is 0, or currency is EUR - no need to convert + if (imp.getBidfloor() == null || imp.getBidfloor().compareTo(BigDecimal.ZERO) == 0 + || currency == BID_CURRENCY) { + return impBuilder.build(); + } + if (currency != null && !currency.isBlank()) { + // if not provided default currency is USD + currency = DEFAULT_ORTB_CURRENCY; + } + + final BigDecimal eurFloor = currencyConversionService.convertCurrency( + imp.getBidfloor(), bidRequest, currency, BID_CURRENCY); + return impBuilder + .bidfloorcur(BID_CURRENCY) + .bidfloor(eurFloor) + .build(); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final BidderError validationError = validate(request); + if (validationError != null) { + return Result.of(Collections.emptyList(), List.of(validationError)); + } + + final List errors = new ArrayList<>(); + final List> httpRequests = new ArrayList<>(); + + final ExtRequestPrebidChannel prebidChannel = getPrebidChannel(request); + final List modifiedImps = new ArrayList<>(request.getImp().size()); + + for (Imp impression : request.getImp()) { + try { + modifiedImps.add(processImpression(request, impression, prebidChannel)); + } catch (Exception e) { + errors.add(BidderError.badInput(e.getMessage())); + continue; + } + } + + httpRequests.add(makeHttpRequest(request.toBuilder().imp(modifiedImps).build())); + return Result.of(httpRequests, errors); + } + + private HttpRequest makeHttpRequest(BidRequest request) { + return BidderUtil.defaultRequest(request, endpointUrl, mapper); + } + + @Override + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + final BidResponse bidResponse; + final int statusCode = httpCall.getResponse().getStatusCode(); + if (statusCode == 204) { + return Result.of(Collections.emptyList(), Collections.emptyList()); + } + if (statusCode != 200) { + return Result.withError(BidderError.badServerResponse( + "Unexpected status code: " + statusCode)); + } + + try { + bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + } catch (DecodeException | PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + + return Result.of(extractBids(bidResponse), Collections.emptyList()); + } + + private List extractBids(BidResponse bidResponse) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + return Collections.emptyList(); + } + + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(Objects::nonNull) + .map(bid -> BidderBid.of(bid, getBidType(bid), bidResponse.getCur())) + .filter(Objects::nonNull) + .toList(); + } + + private BidType getBidType(Bid bid) { + return switch (bid.getMtype()) { + case 1 -> BidType.banner; + case 2 -> BidType.video; + default -> BidType.video; // if not provided video is assumed + }; + } +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java new file mode 100644 index 00000000000..561e1360db9 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java @@ -0,0 +1,14 @@ +package org.prebid.server.proto.openrtb.ext.request.showheroes; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +/** + * Defines the contract for bidRequest.imp[i].ext.showheroes + */ +@Value(staticConstructor = "of") +public class ExtImpShowheroes { + + @JsonProperty("unitId") + String unitId; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java new file mode 100644 index 00000000000..3cd3608b1db --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java @@ -0,0 +1,49 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.showheroes.ShowheroesBidder; +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; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import jakarta.validation.constraints.NotBlank; + +@Configuration +@PropertySource(value = "classpath:/bidder-config/showheroes.yaml", factory = YamlPropertySourceFactory.class) +public class ShowheroesConfiguration { + + private static final String BIDDER_NAME = "showheroes"; + + @Bean("showheroesConfigurationProperties") + @ConfigurationProperties("adapters.showheroes") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps showheroesBidderDeps(BidderConfigurationProperties showheroesConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + CurrencyConversionService currencyConversionService, + PrebidVersionProvider prebidVersionProvider, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(showheroesConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new ShowheroesBidder( + config.getEndpoint(), + currencyConversionService, + prebidVersionProvider, + mapper)) + .assemble(); + } +} diff --git a/src/main/resources/bidder-config/showheroes.yaml b/src/main/resources/bidder-config/showheroes.yaml new file mode 100644 index 00000000000..b5fd953ac71 --- /dev/null +++ b/src/main/resources/bidder-config/showheroes.yaml @@ -0,0 +1,17 @@ +adapters: + showheroes: + endpoint: https://ads.viralize.tv/openrtb2/auction/ + aliases: + showheroes-bs: ~ + showheroesBs: ~ + ortb-version: '2.6' + meta-info: + maintainer-email: tech@showheroes.com + app-media-types: + - banner + - video + site-media-types: + - banner + - video + supported-vendors: + vendor-id: 111 diff --git a/src/main/resources/static/bidder-params/showheroes.json b/src/main/resources/static/bidder-params/showheroes.json new file mode 100644 index 00000000000..fa25d923a52 --- /dev/null +++ b/src/main/resources/static/bidder-params/showheroes.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Showheroes Adapter Params", + "description": "A schema which validates params accepted by the Showheroes adapter", + "type": "object", + "properties": { + "unitId": { + "type": "string", + "description": "Unit ID" + } + }, + "required": ["unitId"] +} diff --git a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java new file mode 100644 index 00000000000..5c7d9db6387 --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java @@ -0,0 +1,377 @@ +package org.prebid.server.bidder.showheroes; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.iab.openrtb.request.App; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.Video; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import org.junit.jupiter.api.Test; +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.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.showheroes.ExtImpShowheroes; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static java.util.Collections.singletonList; +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.prebid.server.proto.openrtb.ext.response.BidType.banner; +import static org.prebid.server.proto.openrtb.ext.response.BidType.video; + +public class ShowheroesBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://ads.showheroes.com/"; + + private final ShowheroesBidder target = new ShowheroesBidder(ENDPOINT_URL, jacksonMapper); + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException().isThrownBy(() -> new ShowheroesBidder("invalid_url", jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { + // given + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).startsWith("Cannot deserialize value"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnErrorWhenUnitIdIsEmpty() { + // given + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpShowheroes.of(""))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1) + .extracting(BidderError::getMessage) + .containsExactly("unitId parameter is required"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnErrorWhenSitePageAndAppBundleAreEmpty() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .site(Site.builder().page("").build()) + .app(App.builder().bundle("").build()) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1) + .extracting(BidderError::getMessage) + .containsExactly("site.page or app.bundle is required"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldCreateCorrectURL() { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue().get(0).getUri()).isEqualTo(ENDPOINT_URL); + } + + @Test + public void makeHttpRequestsShouldConvertCurrencyFromUsdToEur() { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + + final BidRequest outgoingRequest = result.getValue().get(0).getPayload(); + assertThat(outgoingRequest.getCur()).containsExactly("EUR"); + } + + @Test + public void makeHttpRequestsShouldCreateSingleRequestForAllImps() { + // given + final BidRequest bidRequest = BidRequest.builder() + .site(Site.builder().page("https://example.com").build()) + .imp(List.of( + givenImp(impBuilder -> impBuilder.id("imp1")), + givenImp(impBuilder -> impBuilder.id("imp2")))) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + + final BidRequest outgoingRequest = result.getValue().get(0).getPayload(); + assertThat(outgoingRequest.getImp()).hasSize(2) + .extracting(Imp::getId) + .containsExactly("imp1", "imp2"); + } + + @Test + public void makeHttpRequestsShouldSetCorrectHeaders() { + // given + final BidRequest bidRequest = givenBidRequest(identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + + final Map headers = result.getValue().get(0).getHeaders(); + assertThat(headers).containsEntry("Content-Type", "application/json;charset=utf-8") + .containsEntry("Accept", "application/json"); + } + + @Test + public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { + // given + final BidderCall httpCall = givenHttpCall(null, "invalid"); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).startsWith("Failed to decode: Unrecognized token"); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() 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 makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsEmpty() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(BidResponse.builder().seatbid(List.of()).build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnBannerBidIfBannerIsPresentInImp() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("123") + .banner(Banner.builder().build()) + .build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(1)))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(banner); + } + + @Test + public void makeBidsShouldReturnVideoBidIfVideoIsPresentInImp() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("123") + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(2)))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(video); + } + + @Test + public void makeBidsShouldReturnVideoBidByDefaultWhenMtypeIsUnknown() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("123") + .video(Video.builder().build()) + .build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(99)))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(video); + } + + @Test + public void makeBidsShouldReturnCorrectBidderBid() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("123") + .banner(Banner.builder().build()) + .build())) + .build(), + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123").price(BigDecimal.ONE)))); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getBid) + .extracting(Bid::getPrice) + .containsExactly(BigDecimal.ONE); + } + + @Test + public void makeBidsShouldReturnMultipleBids() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + BidRequest.builder() + .imp(List.of( + Imp.builder().id("123").banner(Banner.builder().build()).build(), + Imp.builder().id("456").video(Video.builder().build()).build())) + .build(), + mapper.writeValueAsString(BidResponse.builder() + .seatbid(singletonList(SeatBid.builder() + .bid(List.of( + Bid.builder().impid("123").mtype(1).price(BigDecimal.ONE).build(), + Bid.builder().impid("456").mtype(2).price(BigDecimal.TEN).build())) + .build())) + .build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(2) + .extracting(BidderBid::getBid, BidderBid::getType) + .containsExactlyInAnyOrder( + tuple(Bid.builder().impid("123").mtype(1).price(BigDecimal.ONE).build(), banner), + tuple(Bid.builder().impid("456").mtype(2).price(BigDecimal.TEN).build(), video)); + } + + private static BidRequest givenBidRequest(Function impCustomizer) { + return BidRequest.builder() + .site(Site.builder().page("https://example.com").build()) + .imp(singletonList(givenImp(impCustomizer))) + .build(); + } + + private static Imp givenImp(Function impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("123") + .banner(Banner.builder().build()) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpShowheroes.of("unitId"))))) + .build(); + } + + private static BidResponse givenBidResponse(Function bidCustomizer) { + return BidResponse.builder() + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(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/ShowheroesTest.java b/src/test/java/org/prebid/server/it/ShowheroesTest.java new file mode 100644 index 00000000000..42e6f7a6edb --- /dev/null +++ b/src/test/java/org/prebid/server/it/ShowheroesTest.java @@ -0,0 +1,40 @@ +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 org.prebid.server.version.PrebidVersionProvider; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; + +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 ShowheroesTest extends IntegrationTest { + + @Autowired + private PrebidVersionProvider prebidVersionProvider; + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromShowheroes() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/showheroes-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/showheroes/test-showheroes-bid-request.json", + prebidVersionProvider))) + .willReturn( + aResponse().withBody(jsonFrom("openrtb2/showheroes/test-showheroes-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/showheroes/test-auction-showheroes-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/showheroes/test-auction-showheroes-response.json", response, + singletonList("showheroes")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json new file mode 100644 index 00000000000..5e933b7d15a --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json @@ -0,0 +1,24 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "ext": { + "showheroes": { + "unitId": "12345" + } + }, + "tagid": "tag_id" + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json new file mode 100644 index 00000000000..30c4ff6edf4 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json @@ -0,0 +1,40 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 0.01, + "adid": "adid", + "cid": "cid", + "crid": "crid", + "ext": { + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "showheroes" + } + }, + "origbidcpm": 0.01, + "origbidcur": "USD" + } + } + ], + "seat": "showheroes", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "showheroes": "{{ showheroes.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json new file mode 100644 index 00000000000..9b682c8e388 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json @@ -0,0 +1,57 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 320, + "h": 250 + }, + "tagid": "tag_id", + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "unitId": "1234" + } + } + } + ], + "site": { + "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": ["EUR"], + "source": { + "tid": "${json-unit.any-string}", + "ext": { + "str": "10.0", + "version": "{{ pbs.java.version }}" + } + }, + "regs": { + "gdpr": 0 + }, + "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/showheroes/test-showheroes-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json new file mode 100644 index 00000000000..e9678d08e47 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json @@ -0,0 +1,23 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "crid": "crid", + "adid": "adid", + "price": 0.01, + "id": "bid_id", + "impid": "imp_id", + "cid": "cid", + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "type": "banner" + } + ] +} From 7cf246449d88f6190a4e68050a4952ed0f9bb5b4 Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Mon, 15 Sep 2025 11:43:58 +0200 Subject: [PATCH 2/7] fix tests --- .../bidder/showheroes/ShowheroesBidder.java | 54 ++++++-- .../showheroes/ShowheroesBidderTest.java | 122 +++++++++++++++--- .../test-auction-showheroes-response.json | 1 + .../test-showheroes-bid-request.json | 13 +- .../test-showheroes-bid-response.json | 4 +- .../server/it/test-application.properties | 2 + 6 files changed, 166 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java index f3450069acd..acec4cfb762 100644 --- a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java +++ b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java @@ -1,12 +1,12 @@ package org.prebid.server.bidder.showheroes; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; -import com.iab.openrtb.request.Video; +import com.iab.openrtb.request.Source; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; @@ -25,6 +25,7 @@ import org.prebid.server.proto.openrtb.ext.request.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidChannel; +import org.prebid.server.proto.openrtb.ext.request.ExtSource; import org.prebid.server.proto.openrtb.ext.request.showheroes.ExtImpShowheroes; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.util.BidderUtil; @@ -43,6 +44,7 @@ public class ShowheroesBidder implements Bidder { private static final String BID_CURRENCY = "EUR"; private static final String DEFAULT_ORTB_CURRENCY = "USD"; + private static final String PBSP_JAVA = "java"; private static final TypeReference> SHOWHEROES_EXT_TYPE_REFERENCE = new TypeReference<>() { }; @@ -50,15 +52,18 @@ public class ShowheroesBidder implements Bidder { private final String endpointUrl; private final CurrencyConversionService currencyConversionService; private final JacksonMapper mapper; + private final String pbsVersion; public ShowheroesBidder(String endpointUrl, CurrencyConversionService currencyConversionService, PrebidVersionProvider prebidVersionProvider, JacksonMapper mapper) { - this.endpointUrl = HttpUtil.validateUrlSyntax(Objects.requireNonNull(endpointUrl)); + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); this.currencyConversionService = Objects.requireNonNull(currencyConversionService); this.mapper = Objects.requireNonNull(mapper); + + this.pbsVersion = prebidVersionProvider.getNameVersionRecord(); } private BidderError validate(BidRequest bidRequest) { @@ -83,17 +88,15 @@ private ExtRequestPrebidChannel getPrebidChannel(BidRequest bidRequest) { } private Imp processImpression(BidRequest bidRequest, Imp imp, ExtRequestPrebidChannel prebidChannel) { - final Banner banner = imp.getBanner(); - final Video video = imp.getVideo(); - if (banner == null && video == null) { - throw new PreBidException("Impression must contain one of banner, video or native"); + if (imp.getBanner() == null && imp.getVideo() == null) { + throw new PreBidException("Impression must contain one of banner or video"); } final ExtImpShowheroes extImpShowheroes = mapper.mapper() .convertValue(imp.getExt(), SHOWHEROES_EXT_TYPE_REFERENCE).getBidder(); if (extImpShowheroes == null || extImpShowheroes.getUnitId() == null || extImpShowheroes.getUnitId().isBlank()) { - throw new PreBidException("Ext.imp.bidder.unitId is required"); + throw new PreBidException("unitId is required"); } String channelName = null; @@ -136,6 +139,29 @@ private Imp processImpression(BidRequest bidRequest, Imp imp, ExtRequestPrebidCh .build(); } + private Source getPBSSource(BidRequest bidRequest) { + Source source = bidRequest.getSource(); + if (source == null) { + source = Source.builder().build(); + } + + ExtSource extSource = source.getExt(); + if (extSource == null) { + extSource = ExtSource.of(null); + } + + JsonNode prebidExt = extSource.getProperty("pbs"); + if (prebidExt == null || !prebidExt.isObject()) { + prebidExt = mapper.mapper().createObjectNode(); + } + + ((ObjectNode) prebidExt).put("pbsv", pbsVersion).put("pbsp", PBSP_JAVA); + + extSource.addProperty("pbs", prebidExt); + + return source.toBuilder().ext(extSource).build(); + } + @Override public Result>> makeHttpRequests(BidRequest request) { final BidderError validationError = validate(request); @@ -158,7 +184,15 @@ public Result>> makeHttpRequests(BidRequest request } } - httpRequests.add(makeHttpRequest(request.toBuilder().imp(modifiedImps).build())); + if (modifiedImps.isEmpty()) { + return Result.of(httpRequests, errors); + } + Source source = request.getSource(); + if (pbsVersion != null) { + source = getPBSSource(request); + } + + httpRequests.add(makeHttpRequest(request.toBuilder().imp(modifiedImps).source(source).build())); return Result.of(httpRequests, errors); } @@ -207,7 +241,7 @@ private BidType getBidType(Bid bid) { return switch (bid.getMtype()) { case 1 -> BidType.banner; case 2 -> BidType.video; - default -> BidType.video; // if not provided video is assumed + case null, default -> BidType.video; // if not provided video is assumed }; } } diff --git a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java index 5c7d9db6387..236b716da16 100644 --- a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java @@ -7,10 +7,15 @@ import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Site; import com.iab.openrtb.request.Video; +import com.iab.openrtb.request.Source; 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; @@ -18,14 +23,23 @@ 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.showheroes.ExtImpShowheroes; +import org.prebid.server.version.PrebidVersionProvider; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.function.Function; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.BDDMockito.given; import static java.util.Collections.singletonList; import static java.util.function.Function.identity; import static org.assertj.core.api.Assertions.assertThat; @@ -34,15 +48,30 @@ import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; +@ExtendWith(MockitoExtension.class) public class ShowheroesBidderTest extends VertxTest { private static final String ENDPOINT_URL = "https://ads.showheroes.com/"; - private final ShowheroesBidder target = new ShowheroesBidder(ENDPOINT_URL, jacksonMapper); + private ShowheroesBidder target; + + @Mock(strictness = LENIENT) + private CurrencyConversionService currencyConversionService; + + @Mock(strictness = LENIENT) + private PrebidVersionProvider prebidVersionProvider; + + @BeforeEach + public void setUp() { + // set always 'test_version' as Prebid version for testing + given(prebidVersionProvider.getNameVersionRecord()).willReturn("test_version"); + target = new ShowheroesBidder(ENDPOINT_URL, currencyConversionService, prebidVersionProvider, jacksonMapper); + } @Test public void creationShouldFailOnInvalidEndpointUrl() { - assertThatIllegalArgumentException().isThrownBy(() -> new ShowheroesBidder("invalid_url", jacksonMapper)); + assertThatIllegalArgumentException().isThrownBy(() -> new ShowheroesBidder("invalid_url", + currencyConversionService, prebidVersionProvider, jacksonMapper)); } @Test @@ -74,26 +103,39 @@ public void makeHttpRequestsShouldReturnErrorWhenUnitIdIsEmpty() { // then assertThat(result.getErrors()).hasSize(1) .extracting(BidderError::getMessage) - .containsExactly("unitId parameter is required"); + .containsExactly("unitId is required"); assertThat(result.getValue()).isEmpty(); } @Test - public void makeHttpRequestsShouldReturnErrorWhenSitePageAndAppBundleAreEmpty() { + public void makeHttpRequestsShouldReturnErrorWhenSitePageIsEmpty() { // given final BidRequest bidRequest = BidRequest.builder() .imp(singletonList(givenImp(identity()))) - .site(Site.builder().page("").build()) - .app(App.builder().bundle("").build()) + .site(Site.builder().build()) .build(); // when final Result>> result = target.makeHttpRequests(bidRequest); // then - assertThat(result.getErrors()).hasSize(1) - .extracting(BidderError::getMessage) - .containsExactly("site.page or app.bundle is required"); + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnErrorWhenAppBundleIsEmpty() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .app(App.builder().build()) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1); assertThat(result.getValue()).isEmpty(); } @@ -112,8 +154,7 @@ public void makeHttpRequestsShouldCreateCorrectURL() { } @Test - public void makeHttpRequestsShouldConvertCurrencyFromUsdToEur() { - // given + public void makeHttpRequestsShouldReturnPbsVersion() { final BidRequest bidRequest = givenBidRequest(identity()); // when @@ -121,10 +162,60 @@ public void makeHttpRequestsShouldConvertCurrencyFromUsdToEur() { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getSource) + .extracting(Source::getExt) + .extracting(ext -> ext.getProperty("pbs")) + .containsExactly(mapper.createObjectNode() + .put("pbsv", "test_version") + .put("pbsp", "java")); + } - final BidRequest outgoingRequest = result.getValue().get(0).getPayload(); - assertThat(outgoingRequest.getCur()).containsExactly("EUR"); + @Test + public void makeHttpRequestsShouldConvertCurrencyFromUsdToEur() { + final BidRequest bidRequest = BidRequest.builder() + .imp(List.of(givenImp(impBuilder -> impBuilder.bidfloor(BigDecimal.ONE).bidfloorcur("USD")))) + .app(App.builder().bundle("test_bundle").build()) + .build(); + + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willReturn(BigDecimal.TEN); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + verify(currencyConversionService).convertCurrency(eq(BigDecimal.ONE), any(), eq("USD"), eq("EUR")); + assertThat(result.getValue()).hasSize(1).doesNotContainNull() + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .flatExtracting(BidRequest::getImp).doesNotContainNull() + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsOnly(tuple(BigDecimal.TEN, "EUR")); + } + + @Test + public void makeHttpRequestsShouldNotConvertCurrencyEur() { + final BidRequest bidRequest = BidRequest.builder() + .imp(List.of(givenImp(impBuilder -> impBuilder.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")))) + .app(App.builder().bundle("test_bundle").build()) + .build(); + + given(currencyConversionService.convertCurrency(any(), any(), anyString(), anyString())) + .willReturn(BigDecimal.TEN); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + verify(currencyConversionService, never()).convertCurrency(any(), any(), anyString(), anyString()); + assertThat(result.getValue()).hasSize(1).doesNotContainNull() + .extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class)) + .flatExtracting(BidRequest::getImp).doesNotContainNull() + .extracting(Imp::getBidfloor, Imp::getBidfloorcur) + .containsOnly(tuple(BigDecimal.ONE, "EUR")); } @Test @@ -162,7 +253,8 @@ public void makeHttpRequestsShouldSetCorrectHeaders() { assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1); - final Map headers = result.getValue().get(0).getHeaders(); + final Map headers = result.getValue().get(0).getHeaders().entries().stream() + .collect(java.util.stream.Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); assertThat(headers).containsEntry("Content-Type", "application/json;charset=utf-8") .containsEntry("Accept", "application/json"); } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json index 30c4ff6edf4..74377edd182 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-response.json @@ -11,6 +11,7 @@ "adid": "adid", "cid": "cid", "crid": "crid", + "mtype": 1, "ext": { "prebid": { "type": "banner", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json index 9b682c8e388..14675604e43 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json @@ -12,7 +12,10 @@ "ext": { "tid": "${json-unit.any-string}", "bidder": { - "unitId": "1234" + "unitId": "12345" + }, + "params": { + "unitId": "12345" } } } @@ -33,12 +36,14 @@ }, "at": 1, "tmax": "${json-unit.any-number}", - "cur": ["EUR"], + "cur": ["USD"], "source": { "tid": "${json-unit.any-string}", "ext": { - "str": "10.0", - "version": "{{ pbs.java.version }}" + "pbs": { + "pbsv": "{{ pbs.java.version }}", + "pbsp": "java" + } } }, "regs": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json index e9678d08e47..2dde90a94ce 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-response.json @@ -10,6 +10,7 @@ "id": "bid_id", "impid": "imp_id", "cid": "cid", + "mtype": 1, "ext": { "prebid": { "type": "banner" @@ -19,5 +20,6 @@ ], "type": "banner" } - ] + ], + "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 33a5bccfe4d..1d89a8cad18 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -539,6 +539,8 @@ adapters.sspbc.enabled=true adapters.sspbc.endpoint=http://localhost:8090/sspbc-exchange adapters.sharethrough.enabled=true adapters.sharethrough.endpoint=http://localhost:8090/sharethrough-exchange +adapters.showheroes.enabled=true +adapters.showheroes.endpoint=http://localhost:8090/showheroes-exchange adapters.silvermob.enabled=true adapters.silvermob.endpoint=http://localhost:8090/silvermob-exchange adapters.silverpush.enabled=true From f5186eefc60a370ad7eafd99374bbac32b151358 Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Wed, 17 Sep 2025 17:04:57 +0200 Subject: [PATCH 3/7] adjust code after review --- .../bidder/showheroes/ShowheroesBidder.java | 194 ++++++++---------- .../showheroes/ShowheroesBidderTest.java | 18 -- 2 files changed, 84 insertions(+), 128 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java index acec4cfb762..54bb8533dd4 100644 --- a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java +++ b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java @@ -2,15 +2,18 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Site; import com.iab.openrtb.request.Source; 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.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; @@ -43,7 +46,6 @@ public class ShowheroesBidder implements Bidder { private static final String BID_CURRENCY = "EUR"; - private static final String DEFAULT_ORTB_CURRENCY = "USD"; private static final String PBSP_JAVA = "java"; private static final TypeReference> SHOWHEROES_EXT_TYPE_REFERENCE = new TypeReference<>() { @@ -55,9 +57,9 @@ public class ShowheroesBidder implements Bidder { private final String pbsVersion; public ShowheroesBidder(String endpointUrl, - CurrencyConversionService currencyConversionService, - PrebidVersionProvider prebidVersionProvider, - JacksonMapper mapper) { + CurrencyConversionService currencyConversionService, + PrebidVersionProvider prebidVersionProvider, + JacksonMapper mapper) { this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); this.currencyConversionService = Objects.requireNonNull(currencyConversionService); @@ -66,151 +68,123 @@ public ShowheroesBidder(String endpointUrl, this.pbsVersion = prebidVersionProvider.getNameVersionRecord(); } - private BidderError validate(BidRequest bidRequest) { - // request must contain site object with page or app object with bundle - if (bidRequest.getSite() == null && bidRequest.getApp() == null) { + @Override + public Result>> makeHttpRequests(BidRequest request) { + final BidderError validationError = validate(request.getSite(), request.getApp()); + if (validationError != null) { + return Result.withError(validationError); + } + + final List errors = new ArrayList<>(); + + final ExtRequestPrebidChannel prebidChannel = getPrebidChannel(request); + final List modifiedImps = new ArrayList<>(request.getImp().size()); + + for (Imp impression : request.getImp()) { + try { + modifiedImps.add(modifyImp(request, impression, prebidChannel)); + } catch (Exception e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + + if (modifiedImps.isEmpty()) { + return Result.withErrors(errors); + } + + final Source source = modifySource(request); + final BidRequest modifiedRequest = request.toBuilder().imp(modifiedImps).source(source).build(); + final HttpRequest httpRequest = BidderUtil.defaultRequest(modifiedRequest, endpointUrl, mapper); + + return Result.of(Collections.singletonList(httpRequest), errors); + } + + private static BidderError validate(Site site, App app) { + if (site == null && app == null) { return BidderError.badInput("BidRequest must contain one of site or app"); } - if (bidRequest.getSite() != null && bidRequest.getSite().getPage() == null) { + if (site != null && site.getPage() == null) { return BidderError.badInput("BidRequest.site.page is required"); } - if (bidRequest.getApp() != null && bidRequest.getApp().getBundle() == null) { + if (app != null && app.getBundle() == null) { return BidderError.badInput("BidRequest.app.bundle is required"); } return null; } - private ExtRequestPrebidChannel getPrebidChannel(BidRequest bidRequest) { + private static ExtRequestPrebidChannel getPrebidChannel(BidRequest bidRequest) { return Optional.ofNullable(bidRequest.getExt()) .map(ExtRequest::getPrebid) .map(ExtRequestPrebid::getChannel) .orElse(null); } - private Imp processImpression(BidRequest bidRequest, Imp imp, ExtRequestPrebidChannel prebidChannel) { - if (imp.getBanner() == null && imp.getVideo() == null) { - throw new PreBidException("Impression must contain one of banner or video"); - } - - final ExtImpShowheroes extImpShowheroes = mapper.mapper() - .convertValue(imp.getExt(), SHOWHEROES_EXT_TYPE_REFERENCE).getBidder(); - if (extImpShowheroes == null || extImpShowheroes.getUnitId() == null - || extImpShowheroes.getUnitId().isBlank()) { - throw new PreBidException("unitId is required"); - } - - String channelName = null; - String channelVersion = null; - if (prebidChannel != null) { - channelName = prebidChannel.getName(); - channelVersion = prebidChannel.getVersion(); - } - - final ObjectNode impExt = imp.getExt(); + private Imp modifyImp(BidRequest bidRequest, Imp imp, ExtRequestPrebidChannel prebidChannel) { + final ExtImpShowheroes extImpShowheroes = parseImpExt(imp); final Imp.ImpBuilder impBuilder = imp.toBuilder(); - // copy unitId from ext.bidder to ext.params - impExt.set("params", JsonNodeFactory.instance.objectNode() - .put("unitId", extImpShowheroes.getUnitId())); - - impBuilder.ext(impExt); - if (imp.getDisplaymanager() == null && channelName != null) { - impBuilder.displaymanager(channelName); - impBuilder.displaymanagerver(channelVersion); + if (prebidChannel != null && imp.getDisplaymanager() == null) { + impBuilder.displaymanager(prebidChannel.getName()); + impBuilder.displaymanagerver(prebidChannel.getVersion()); } - String currency = imp.getBidfloorcur(); - // if floor price is 0, or currency is EUR - no need to convert - if (imp.getBidfloor() == null || imp.getBidfloor().compareTo(BigDecimal.ZERO) == 0 - || currency == BID_CURRENCY) { + impBuilder.ext(modifyImpExt(imp, extImpShowheroes)); + + if (!shouldConvertFloor(imp)) { return impBuilder.build(); } - if (currency != null && !currency.isBlank()) { - // if not provided default currency is USD - currency = DEFAULT_ORTB_CURRENCY; - } - final BigDecimal eurFloor = currencyConversionService.convertCurrency( - imp.getBidfloor(), bidRequest, currency, BID_CURRENCY); return impBuilder .bidfloorcur(BID_CURRENCY) - .bidfloor(eurFloor) + .bidfloor(resolveBidFloor(bidRequest, imp)) .build(); } - private Source getPBSSource(BidRequest bidRequest) { - Source source = bidRequest.getSource(); - if (source == null) { - source = Source.builder().build(); - } - - ExtSource extSource = source.getExt(); - if (extSource == null) { - extSource = ExtSource.of(null); - } - - JsonNode prebidExt = extSource.getProperty("pbs"); - if (prebidExt == null || !prebidExt.isObject()) { - prebidExt = mapper.mapper().createObjectNode(); + private ExtImpShowheroes parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), SHOWHEROES_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage()); } - - ((ObjectNode) prebidExt).put("pbsv", pbsVersion).put("pbsp", PBSP_JAVA); - - extSource.addProperty("pbs", prebidExt); - - return source.toBuilder().ext(extSource).build(); } - @Override - public Result>> makeHttpRequests(BidRequest request) { - final BidderError validationError = validate(request); - if (validationError != null) { - return Result.of(Collections.emptyList(), List.of(validationError)); - } - - final List errors = new ArrayList<>(); - final List> httpRequests = new ArrayList<>(); + private ObjectNode modifyImpExt(Imp imp, ExtImpShowheroes shImpExt) { + final ObjectNode impExt = ObjectUtils.defaultIfNull(imp.getExt(), mapper.mapper().createObjectNode()); + impExt.set("params", mapper.mapper().createObjectNode().put("unitId", shImpExt.getUnitId())); + return impExt; + } - final ExtRequestPrebidChannel prebidChannel = getPrebidChannel(request); - final List modifiedImps = new ArrayList<>(request.getImp().size()); + private static boolean shouldConvertFloor(Imp imp) { + return BidderUtil.isValidPrice(imp.getBidfloor()) + && !StringUtils.equalsIgnoreCase(imp.getBidfloorcur(), BID_CURRENCY); + } - for (Imp impression : request.getImp()) { - try { - modifiedImps.add(processImpression(request, impression, prebidChannel)); - } catch (Exception e) { - errors.add(BidderError.badInput(e.getMessage())); - continue; - } - } + private BigDecimal resolveBidFloor(BidRequest bidRequest, Imp imp) { + return currencyConversionService.convertCurrency( + imp.getBidfloor(), bidRequest, imp.getBidfloorcur(), BID_CURRENCY); + } - if (modifiedImps.isEmpty()) { - return Result.of(httpRequests, errors); + private Source modifySource(BidRequest bidRequest) { + if (pbsVersion == null) { + return bidRequest.getSource(); } - Source source = request.getSource(); - if (pbsVersion != null) { - source = getPBSSource(request); - } - - httpRequests.add(makeHttpRequest(request.toBuilder().imp(modifiedImps).source(source).build())); - return Result.of(httpRequests, errors); - } + final Source source = ObjectUtils.defaultIfNull(bidRequest.getSource(), Source.builder().build()); + final ExtSource extSource = ObjectUtils.defaultIfNull(source.getExt(), ExtSource.of(null)); + final ObjectNode prebidExtSource = Optional.ofNullable(extSource.getProperty("pbs")) + .filter(JsonNode::isObject) + .map(ObjectNode.class::cast) + .orElseGet(() -> mapper.mapper().createObjectNode()) + .put("pbsv", pbsVersion) + .put("pbsp", PBSP_JAVA); - private HttpRequest makeHttpRequest(BidRequest request) { - return BidderUtil.defaultRequest(request, endpointUrl, mapper); + extSource.addProperty("pbs", prebidExtSource); + return source.toBuilder().ext(extSource).build(); } @Override public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { final BidResponse bidResponse; - final int statusCode = httpCall.getResponse().getStatusCode(); - if (statusCode == 204) { - return Result.of(Collections.emptyList(), Collections.emptyList()); - } - if (statusCode != 200) { - return Result.withError(BidderError.badServerResponse( - "Unexpected status code: " + statusCode)); - } try { bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); @@ -241,7 +215,7 @@ private BidType getBidType(Bid bid) { return switch (bid.getMtype()) { case 1 -> BidType.banner; case 2 -> BidType.video; - case null, default -> BidType.video; // if not provided video is assumed + case null, default -> BidType.video; }; } } diff --git a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java index 236b716da16..8781f78cfac 100644 --- a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java @@ -19,7 +19,6 @@ 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; @@ -90,23 +89,6 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { assertThat(result.getValue()).isEmpty(); } - @Test - public void makeHttpRequestsShouldReturnErrorWhenUnitIdIsEmpty() { - // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpShowheroes.of(""))))); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).hasSize(1) - .extracting(BidderError::getMessage) - .containsExactly("unitId is required"); - assertThat(result.getValue()).isEmpty(); - } - @Test public void makeHttpRequestsShouldReturnErrorWhenSitePageIsEmpty() { // given From d21324c262d8b93780223f5706aff8e0321f0359 Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Wed, 17 Sep 2025 17:29:20 +0200 Subject: [PATCH 4/7] adjust tests after code review --- .../bidder/ShowheroesConfiguration.java | 8 +- .../showheroes/ShowheroesBidderTest.java | 92 +++++-------------- 2 files changed, 26 insertions(+), 74 deletions(-) diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java index 3cd3608b1db..260d8376675 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/ShowheroesConfiguration.java @@ -31,10 +31,10 @@ BidderConfigurationProperties configurationProperties() { @Bean BidderDeps showheroesBidderDeps(BidderConfigurationProperties showheroesConfigurationProperties, - @NotBlank @Value("${external-url}") String externalUrl, - CurrencyConversionService currencyConversionService, - PrebidVersionProvider prebidVersionProvider, - JacksonMapper mapper) { + @NotBlank @Value("${external-url}") String externalUrl, + CurrencyConversionService currencyConversionService, + PrebidVersionProvider prebidVersionProvider, + JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) .withConfig(showheroesConfigurationProperties) diff --git a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java index 8781f78cfac..aeb36e265dd 100644 --- a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java @@ -6,7 +6,6 @@ import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Site; -import com.iab.openrtb.request.Video; import com.iab.openrtb.request.Source; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; @@ -204,7 +203,7 @@ public void makeHttpRequestsShouldNotConvertCurrencyEur() { public void makeHttpRequestsShouldCreateSingleRequestForAllImps() { // given final BidRequest bidRequest = BidRequest.builder() - .site(Site.builder().page("https://example.com").build()) + .site(Site.builder().page("https://test-example.com").build()) .imp(List.of( givenImp(impBuilder -> impBuilder.id("imp1")), givenImp(impBuilder -> impBuilder.id("imp2")))) @@ -241,24 +240,10 @@ public void makeHttpRequestsShouldSetCorrectHeaders() { .containsEntry("Accept", "application/json"); } - @Test - public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { - // given - final BidderCall httpCall = givenHttpCall(null, "invalid"); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).hasSize(1); - assertThat(result.getErrors().get(0).getMessage()).startsWith("Failed to decode: Unrecognized token"); - assertThat(result.getValue()).isEmpty(); - } - @Test public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null)); + final BidderCall httpCall = givenHttpCall(null); // when final Result> result = target.makeBids(httpCall, null); @@ -271,8 +256,7 @@ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProces @Test public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(BidResponse.builder().build())); + final BidderCall httpCall = givenHttpCall(BidResponse.builder().build()); // when final Result> result = target.makeBids(httpCall, null); @@ -285,8 +269,7 @@ public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws Jso @Test public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsEmpty() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(BidResponse.builder().seatbid(List.of()).build())); + final BidderCall httpCall = givenHttpCall(BidResponse.builder().seatbid(List.of()).build()); // when final Result> result = target.makeBids(httpCall, null); @@ -300,14 +283,7 @@ public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsEmpty() throws Js public void makeBidsShouldReturnBannerBidIfBannerIsPresentInImp() throws JsonProcessingException { // given final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("123") - .banner(Banner.builder().build()) - .build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(1)))); + givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(1))); // when final Result> result = target.makeBids(httpCall, null); @@ -315,22 +291,15 @@ public void makeBidsShouldReturnBannerBidIfBannerIsPresentInImp() throws JsonPro // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .extracting(BidderBid::getType) - .containsExactly(banner); + .extracting(BidderBid::getBid, BidderBid::getType) + .containsExactly(tuple(Bid.builder().impid("123").mtype(1).build(), banner)); } @Test public void makeBidsShouldReturnVideoBidIfVideoIsPresentInImp() throws JsonProcessingException { // given final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("123") - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(2)))); + givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(2))); // when final Result> result = target.makeBids(httpCall, null); @@ -338,22 +307,15 @@ public void makeBidsShouldReturnVideoBidIfVideoIsPresentInImp() throws JsonProce // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .extracting(BidderBid::getType) - .containsExactly(video); + .extracting(BidderBid::getBid, BidderBid::getType) + .containsExactly(tuple(Bid.builder().impid("123").mtype(2).build(), video)); } @Test public void makeBidsShouldReturnVideoBidByDefaultWhenMtypeIsUnknown() throws JsonProcessingException { // given final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("123") - .video(Video.builder().build()) - .build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(99)))); + givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(99))); // when final Result> result = target.makeBids(httpCall, null); @@ -361,22 +323,15 @@ public void makeBidsShouldReturnVideoBidByDefaultWhenMtypeIsUnknown() throws Jso // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .extracting(BidderBid::getType) - .containsExactly(video); + .extracting(BidderBid::getBid, BidderBid::getType) + .containsExactly(tuple(Bid.builder().impid("123").mtype(99).build(), video)); } @Test public void makeBidsShouldReturnCorrectBidderBid() throws JsonProcessingException { // given final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("123") - .banner(Banner.builder().build()) - .build())) - .build(), - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").price(BigDecimal.ONE)))); + givenBidResponse(bidBuilder -> bidBuilder.impid("123").price(BigDecimal.ONE))); // when final Result> result = target.makeBids(httpCall, null); @@ -393,18 +348,13 @@ public void makeBidsShouldReturnCorrectBidderBid() throws JsonProcessingExceptio public void makeBidsShouldReturnMultipleBids() throws JsonProcessingException { // given final BidderCall httpCall = givenHttpCall( - BidRequest.builder() - .imp(List.of( - Imp.builder().id("123").banner(Banner.builder().build()).build(), - Imp.builder().id("456").video(Video.builder().build()).build())) - .build(), - mapper.writeValueAsString(BidResponse.builder() + BidResponse.builder() .seatbid(singletonList(SeatBid.builder() .bid(List.of( Bid.builder().impid("123").mtype(1).price(BigDecimal.ONE).build(), Bid.builder().impid("456").mtype(2).price(BigDecimal.TEN).build())) .build())) - .build())); + .build()); // when final Result> result = target.makeBids(httpCall, null); @@ -420,7 +370,7 @@ public void makeBidsShouldReturnMultipleBids() throws JsonProcessingException { private static BidRequest givenBidRequest(Function impCustomizer) { return BidRequest.builder() - .site(Site.builder().page("https://example.com").build()) + .site(Site.builder().page("https://test-example.com").build()) .imp(singletonList(givenImp(impCustomizer))) .build(); } @@ -441,10 +391,12 @@ private static BidResponse givenBidResponse(Function givenHttpCall(BidRequest bidRequest, String body) { + private static BidderCall givenHttpCall(BidResponse bidResponse) + throws JsonProcessingException { + return BidderCall.succeededHttp( - HttpRequest.builder().payload(bidRequest).build(), - HttpResponse.of(200, null, body), + null, + HttpResponse.of(200, null, mapper.writeValueAsString(bidResponse)), null); } } From 4ee05b4536c9585532f3cbd79e1109f92eb72423 Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Wed, 17 Sep 2025 18:13:10 +0200 Subject: [PATCH 5/7] add tests for showheroes alias --- .../prebid/server/it/ShowheroesBSTest.java | 40 ++++++++++++ .../test-auction-showheroes-request.json | 24 +++++++ .../test-auction-showheroes-response.json | 41 ++++++++++++ .../test-showheroes-bid-request.json | 62 +++++++++++++++++++ .../test-showheroes-bid-response.json | 25 ++++++++ .../server/it/test-application.properties | 2 + 6 files changed, 194 insertions(+) create mode 100644 src/test/java/org/prebid/server/it/ShowheroesBSTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-response.json diff --git a/src/test/java/org/prebid/server/it/ShowheroesBSTest.java b/src/test/java/org/prebid/server/it/ShowheroesBSTest.java new file mode 100644 index 00000000000..00b8c89f471 --- /dev/null +++ b/src/test/java/org/prebid/server/it/ShowheroesBSTest.java @@ -0,0 +1,40 @@ +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 org.prebid.server.version.PrebidVersionProvider; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; + +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 ShowheroesBSTest extends IntegrationTest { + + @Autowired + private PrebidVersionProvider prebidVersionProvider; + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromShowheroesBS() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/showheroesbs-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/showheroesBs/test-showheroes-bid-request.json", + prebidVersionProvider))) + .willReturn( + aResponse().withBody(jsonFrom("openrtb2/showheroesBs/test-showheroes-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/showheroesBs/test-auction-showheroes-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/showheroesBs/test-auction-showheroes-response.json", response, + singletonList("showheroesBs")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json new file mode 100644 index 00000000000..6422a006cad --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json @@ -0,0 +1,24 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "ext": { + "showheroesBs": { + "unitId": "12345" + } + }, + "tagid": "tag_id" + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-response.json new file mode 100644 index 00000000000..2655490d994 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-response.json @@ -0,0 +1,41 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 0.01, + "adid": "adid", + "cid": "cid", + "crid": "crid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "showheroesBs" + } + }, + "origbidcpm": 0.01, + "origbidcur": "USD" + } + } + ], + "seat": "showheroesBs", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "showheroesBs": "{{ showheroesBs.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json new file mode 100644 index 00000000000..14675604e43 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json @@ -0,0 +1,62 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 320, + "h": 250 + }, + "tagid": "tag_id", + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "unitId": "12345" + }, + "params": { + "unitId": "12345" + } + } + } + ], + "site": { + "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"], + "source": { + "tid": "${json-unit.any-string}", + "ext": { + "pbs": { + "pbsv": "{{ pbs.java.version }}", + "pbsp": "java" + } + } + }, + "regs": { + "gdpr": 0 + }, + "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/showheroesBs/test-showheroes-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-response.json new file mode 100644 index 00000000000..2dde90a94ce --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-response.json @@ -0,0 +1,25 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "crid": "crid", + "adid": "adid", + "price": 0.01, + "id": "bid_id", + "impid": "imp_id", + "cid": "cid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "type": "banner" + } + ], + "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 1d89a8cad18..268be448c6e 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -541,6 +541,8 @@ adapters.sharethrough.enabled=true adapters.sharethrough.endpoint=http://localhost:8090/sharethrough-exchange adapters.showheroes.enabled=true adapters.showheroes.endpoint=http://localhost:8090/showheroes-exchange +adapters.showheroes.aliases.showheroesBs.enabled=true +adapters.showheroes.aliases.showheroesBs.endpoint=http://localhost:8090/showheroesbs-exchange adapters.silvermob.enabled=true adapters.silvermob.endpoint=http://localhost:8090/silvermob-exchange adapters.silverpush.enabled=true From ae6260f0dec3e8d432b3405b2ee63c0a6bf424da Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Fri, 19 Sep 2025 15:46:23 +0200 Subject: [PATCH 6/7] add tests for 2nd alias and resolve comments --- .../bidder/showheroes/ShowheroesBidder.java | 6 +- .../request/showheroes/ExtImpShowheroes.java | 3 - .../static/bidder-params/showheroes.json | 3 +- .../showheroes/ShowheroesBidderTest.java | 79 +++++-------------- .../prebid/server/it/ShowheroesBSTest.java | 2 +- .../prebid/server/it/ShowheroesbsTest.java | 40 ++++++++++ .../test-auction-showheroes-request.json | 2 +- .../test-showheroes-bid-request.json | 4 +- .../test-auction-showheroes-request.json | 2 +- .../test-showheroes-bid-request.json | 4 +- .../test-auction-showheroes-request.json | 24 ++++++ .../test-auction-showheroes-response.json | 41 ++++++++++ .../test-showheroes-bid-request.json | 62 +++++++++++++++ .../test-showheroes-bid-response.json | 25 ++++++ .../server/it/test-application.properties | 2 +- 15 files changed, 223 insertions(+), 76 deletions(-) create mode 100644 src/test/java/org/prebid/server/it/ShowheroesbsTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java index 54bb8533dd4..443afb3e005 100644 --- a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java +++ b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java @@ -184,15 +184,13 @@ private Source modifySource(BidRequest bidRequest) { @Override public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { - final BidResponse bidResponse; - try { - bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + return Result.of(extractBids(bidResponse), Collections.emptyList()); } catch (DecodeException | PreBidException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } - return Result.of(extractBids(bidResponse), Collections.emptyList()); } private List extractBids(BidResponse bidResponse) { diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java index 561e1360db9..77b65257fb0 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/showheroes/ExtImpShowheroes.java @@ -3,9 +3,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Value; -/** - * Defines the contract for bidRequest.imp[i].ext.showheroes - */ @Value(staticConstructor = "of") public class ExtImpShowheroes { diff --git a/src/main/resources/static/bidder-params/showheroes.json b/src/main/resources/static/bidder-params/showheroes.json index fa25d923a52..3c269d118d8 100644 --- a/src/main/resources/static/bidder-params/showheroes.json +++ b/src/main/resources/static/bidder-params/showheroes.json @@ -6,7 +6,8 @@ "properties": { "unitId": { "type": "string", - "description": "Unit ID" + "description": "Unit ID", + "minLength": 8 } }, "required": ["unitId"] diff --git a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java index aeb36e265dd..2e109007149 100644 --- a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java @@ -28,7 +28,7 @@ import java.math.BigDecimal; import java.util.List; -import java.util.Map; +import java.util.Set; import java.util.function.Function; import static org.mockito.ArgumentMatchers.any; @@ -45,23 +45,26 @@ import static org.assertj.core.api.Assertions.tuple; 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.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; +import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; @ExtendWith(MockitoExtension.class) public class ShowheroesBidderTest extends VertxTest { private static final String ENDPOINT_URL = "https://ads.showheroes.com/"; - private ShowheroesBidder target; - @Mock(strictness = LENIENT) private CurrencyConversionService currencyConversionService; @Mock(strictness = LENIENT) private PrebidVersionProvider prebidVersionProvider; + private ShowheroesBidder target; + @BeforeEach public void setUp() { - // set always 'test_version' as Prebid version for testing given(prebidVersionProvider.getNameVersionRecord()).willReturn("test_version"); target = new ShowheroesBidder(ENDPOINT_URL, currencyConversionService, prebidVersionProvider, jacksonMapper); } @@ -136,6 +139,7 @@ public void makeHttpRequestsShouldCreateCorrectURL() { @Test public void makeHttpRequestsShouldReturnPbsVersion() { + // given final BidRequest bidRequest = givenBidRequest(identity()); // when @@ -155,6 +159,7 @@ public void makeHttpRequestsShouldReturnPbsVersion() { @Test public void makeHttpRequestsShouldConvertCurrencyFromUsdToEur() { + // given final BidRequest bidRequest = BidRequest.builder() .imp(List.of(givenImp(impBuilder -> impBuilder.bidfloor(BigDecimal.ONE).bidfloorcur("USD")))) .app(App.builder().bundle("test_bundle").build()) @@ -178,6 +183,7 @@ public void makeHttpRequestsShouldConvertCurrencyFromUsdToEur() { @Test public void makeHttpRequestsShouldNotConvertCurrencyEur() { + // given final BidRequest bidRequest = BidRequest.builder() .imp(List.of(givenImp(impBuilder -> impBuilder.bidfloor(BigDecimal.ONE).bidfloorcur("EUR")))) .app(App.builder().bundle("test_bundle").build()) @@ -214,7 +220,9 @@ public void makeHttpRequestsShouldCreateSingleRequestForAllImps() { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getImpIds) + .containsOnly(Set.of("imp1", "imp2")); final BidRequest outgoingRequest = result.getValue().get(0).getPayload(); assertThat(outgoingRequest.getImp()).hasSize(2) @@ -232,12 +240,12 @@ public void makeHttpRequestsShouldSetCorrectHeaders() { // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); - - final Map headers = result.getValue().get(0).getHeaders().entries().stream() - .collect(java.util.stream.Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - assertThat(headers).containsEntry("Content-Type", "application/json;charset=utf-8") - .containsEntry("Accept", "application/json"); + 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)); } @Test @@ -279,38 +287,6 @@ public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsEmpty() throws Js assertThat(result.getValue()).isEmpty(); } - @Test - public void makeBidsShouldReturnBannerBidIfBannerIsPresentInImp() throws JsonProcessingException { - // given - final BidderCall httpCall = givenHttpCall( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(1))); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(BidderBid::getBid, BidderBid::getType) - .containsExactly(tuple(Bid.builder().impid("123").mtype(1).build(), banner)); - } - - @Test - public void makeBidsShouldReturnVideoBidIfVideoIsPresentInImp() throws JsonProcessingException { - // given - final BidderCall httpCall = givenHttpCall( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").mtype(2))); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(BidderBid::getBid, BidderBid::getType) - .containsExactly(tuple(Bid.builder().impid("123").mtype(2).build(), video)); - } - @Test public void makeBidsShouldReturnVideoBidByDefaultWhenMtypeIsUnknown() throws JsonProcessingException { // given @@ -327,23 +303,6 @@ public void makeBidsShouldReturnVideoBidByDefaultWhenMtypeIsUnknown() throws Jso .containsExactly(tuple(Bid.builder().impid("123").mtype(99).build(), video)); } - @Test - public void makeBidsShouldReturnCorrectBidderBid() throws JsonProcessingException { - // given - final BidderCall httpCall = givenHttpCall( - givenBidResponse(bidBuilder -> bidBuilder.impid("123").price(BigDecimal.ONE))); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(BidderBid::getBid) - .extracting(Bid::getPrice) - .containsExactly(BigDecimal.ONE); - } - @Test public void makeBidsShouldReturnMultipleBids() throws JsonProcessingException { // given diff --git a/src/test/java/org/prebid/server/it/ShowheroesBSTest.java b/src/test/java/org/prebid/server/it/ShowheroesBSTest.java index 00b8c89f471..c291bceedb9 100644 --- a/src/test/java/org/prebid/server/it/ShowheroesBSTest.java +++ b/src/test/java/org/prebid/server/it/ShowheroesBSTest.java @@ -23,7 +23,7 @@ public class ShowheroesBSTest extends IntegrationTest { @Test public void openrtb2AuctionShouldRespondWithBidsFromShowheroesBS() throws IOException, JSONException { // given - WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/showheroesbs-exchange")) + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/showheroes-exchange")) .withRequestBody(equalToJson(jsonFrom("openrtb2/showheroesBs/test-showheroes-bid-request.json", prebidVersionProvider))) .willReturn( diff --git a/src/test/java/org/prebid/server/it/ShowheroesbsTest.java b/src/test/java/org/prebid/server/it/ShowheroesbsTest.java new file mode 100644 index 00000000000..2d60a348ae2 --- /dev/null +++ b/src/test/java/org/prebid/server/it/ShowheroesbsTest.java @@ -0,0 +1,40 @@ +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 org.prebid.server.version.PrebidVersionProvider; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; + +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 ShowheroesbsTest extends IntegrationTest { + + @Autowired + private PrebidVersionProvider prebidVersionProvider; + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromShowheroesbs() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/showheroes-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/showheroes_bs/test-showheroes-bid-request.json", + prebidVersionProvider))) + .willReturn( + aResponse().withBody(jsonFrom("openrtb2/showheroes_bs/test-showheroes-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/showheroes_bs/test-auction-showheroes-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/showheroes_bs/test-auction-showheroes-response.json", response, + singletonList("showheroes-bs")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json index 5e933b7d15a..2f50c3466e7 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-auction-showheroes-request.json @@ -9,7 +9,7 @@ }, "ext": { "showheroes": { - "unitId": "12345" + "unitId": "12345678" } }, "tagid": "tag_id" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json index 14675604e43..7b0681643f0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes/test-showheroes-bid-request.json @@ -12,10 +12,10 @@ "ext": { "tid": "${json-unit.any-string}", "bidder": { - "unitId": "12345" + "unitId": "12345678" }, "params": { - "unitId": "12345" + "unitId": "12345678" } } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json index 6422a006cad..33078a75ab3 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-auction-showheroes-request.json @@ -9,7 +9,7 @@ }, "ext": { "showheroesBs": { - "unitId": "12345" + "unitId": "12345678" } }, "tagid": "tag_id" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json index 14675604e43..7b0681643f0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroesBs/test-showheroes-bid-request.json @@ -12,10 +12,10 @@ "ext": { "tid": "${json-unit.any-string}", "bidder": { - "unitId": "12345" + "unitId": "12345678" }, "params": { - "unitId": "12345" + "unitId": "12345678" } } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-request.json new file mode 100644 index 00000000000..e44185fb537 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-request.json @@ -0,0 +1,24 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 320, + "h": 250 + }, + "ext": { + "showheroes-bs": { + "unitId": "12345678" + } + }, + "tagid": "tag_id" + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-response.json new file mode 100644 index 00000000000..53ce08b83ab --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-auction-showheroes-response.json @@ -0,0 +1,41 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 0.01, + "adid": "adid", + "cid": "cid", + "crid": "crid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "showheroes-bs" + } + }, + "origbidcpm": 0.01, + "origbidcur": "USD" + } + } + ], + "seat": "showheroes-bs", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "showheroes-bs": "{{ showheroes-bs.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-request.json new file mode 100644 index 00000000000..7b0681643f0 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-request.json @@ -0,0 +1,62 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 320, + "h": 250 + }, + "tagid": "tag_id", + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "unitId": "12345678" + }, + "params": { + "unitId": "12345678" + } + } + } + ], + "site": { + "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"], + "source": { + "tid": "${json-unit.any-string}", + "ext": { + "pbs": { + "pbsv": "{{ pbs.java.version }}", + "pbsp": "java" + } + } + }, + "regs": { + "gdpr": 0 + }, + "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/showheroes_bs/test-showheroes-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-response.json new file mode 100644 index 00000000000..2dde90a94ce --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/showheroes_bs/test-showheroes-bid-response.json @@ -0,0 +1,25 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "crid": "crid", + "adid": "adid", + "price": 0.01, + "id": "bid_id", + "impid": "imp_id", + "cid": "cid", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "type": "banner" + } + ], + "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 268be448c6e..667aea12a02 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -542,7 +542,7 @@ adapters.sharethrough.endpoint=http://localhost:8090/sharethrough-exchange adapters.showheroes.enabled=true adapters.showheroes.endpoint=http://localhost:8090/showheroes-exchange adapters.showheroes.aliases.showheroesBs.enabled=true -adapters.showheroes.aliases.showheroesBs.endpoint=http://localhost:8090/showheroesbs-exchange +adapters.showheroes.aliases.showheroes-bs.enabled=true adapters.silvermob.enabled=true adapters.silvermob.endpoint=http://localhost:8090/silvermob-exchange adapters.silverpush.enabled=true From fb04e9f01671d75ff5e1dbdb67bc993896778cc2 Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Thu, 25 Sep 2025 13:57:49 +0200 Subject: [PATCH 7/7] refactor tests after review --- .../bidder/showheroes/ShowheroesBidder.java | 29 +++++++--------- .../showheroes/ShowheroesBidderTest.java | 33 +++++++++---------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java index 443afb3e005..1919fa82b01 100644 --- a/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java +++ b/src/main/java/org/prebid/server/bidder/showheroes/ShowheroesBidder.java @@ -122,22 +122,15 @@ private static ExtRequestPrebidChannel getPrebidChannel(BidRequest bidRequest) { private Imp modifyImp(BidRequest bidRequest, Imp imp, ExtRequestPrebidChannel prebidChannel) { final ExtImpShowheroes extImpShowheroes = parseImpExt(imp); - final Imp.ImpBuilder impBuilder = imp.toBuilder(); - - if (prebidChannel != null && imp.getDisplaymanager() == null) { - impBuilder.displaymanager(prebidChannel.getName()); - impBuilder.displaymanagerver(prebidChannel.getVersion()); - } - - impBuilder.ext(modifyImpExt(imp, extImpShowheroes)); - - if (!shouldConvertFloor(imp)) { - return impBuilder.build(); - } - - return impBuilder - .bidfloorcur(BID_CURRENCY) - .bidfloor(resolveBidFloor(bidRequest, imp)) + final boolean shouldSetDisplayManager = prebidChannel != null && imp.getDisplaymanager() == null; + final boolean shouldConvertFloor = shouldConvertFloor(imp); + + return imp.toBuilder() + .displaymanager(shouldSetDisplayManager ? prebidChannel.getName() : imp.getDisplaymanager()) + .displaymanagerver(shouldSetDisplayManager ? prebidChannel.getVersion() : imp.getDisplaymanagerver()) + .bidfloorcur(shouldConvertFloor ? BID_CURRENCY : imp.getBidfloorcur()) + .bidfloor(shouldConvertFloor ? resolveBidFloor(bidRequest, imp) : imp.getBidfloor()) + .ext(modifyImpExt(imp, extImpShowheroes)) .build(); } @@ -187,7 +180,7 @@ public Result> makeBids(BidderCall httpCall, BidRequ try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); return Result.of(extractBids(bidResponse), Collections.emptyList()); - } catch (DecodeException | PreBidException e) { + } catch (DecodeException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } @@ -209,7 +202,7 @@ private List extractBids(BidResponse bidResponse) { .toList(); } - private BidType getBidType(Bid bid) { + private static BidType getBidType(Bid bid) { return switch (bid.getMtype()) { case 1 -> BidType.banner; case 2 -> BidType.video; diff --git a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java index 2e109007149..acb15b91bb5 100644 --- a/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/showheroes/ShowheroesBidderTest.java @@ -28,7 +28,6 @@ import java.math.BigDecimal; import java.util.List; -import java.util.Set; import java.util.function.Function; import static org.mockito.ArgumentMatchers.any; @@ -78,9 +77,12 @@ public void creationShouldFailOnInvalidEndpointUrl() { @Test public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder - .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + final BidRequest bidRequest = BidRequest.builder() + .site(Site.builder().page("https://test-example.com").build()) + .imp(singletonList(Imp.builder() + .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))) + .build())) + .build(); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -104,6 +106,7 @@ public void makeHttpRequestsShouldReturnErrorWhenSitePageIsEmpty() { // then assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).isEqualTo("BidRequest.site.page is required"); assertThat(result.getValue()).isEmpty(); } @@ -120,13 +123,14 @@ public void makeHttpRequestsShouldReturnErrorWhenAppBundleIsEmpty() { // then assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().get(0).getMessage()).isEqualTo("BidRequest.app.bundle is required"); assertThat(result.getValue()).isEmpty(); } @Test public void makeHttpRequestsShouldCreateCorrectURL() { // given - final BidRequest bidRequest = givenBidRequest(identity()); + final BidRequest bidRequest = givenBidRequest(); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -140,7 +144,7 @@ public void makeHttpRequestsShouldCreateCorrectURL() { @Test public void makeHttpRequestsShouldReturnPbsVersion() { // given - final BidRequest bidRequest = givenBidRequest(identity()); + final BidRequest bidRequest = givenBidRequest(); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -221,11 +225,8 @@ public void makeHttpRequestsShouldCreateSingleRequestForAllImps() { // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getImpIds) - .containsOnly(Set.of("imp1", "imp2")); - - final BidRequest outgoingRequest = result.getValue().get(0).getPayload(); - assertThat(outgoingRequest.getImp()).hasSize(2) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) .extracting(Imp::getId) .containsExactly("imp1", "imp2"); } @@ -233,7 +234,7 @@ public void makeHttpRequestsShouldCreateSingleRequestForAllImps() { @Test public void makeHttpRequestsShouldSetCorrectHeaders() { // given - final BidRequest bidRequest = givenBidRequest(identity()); + final BidRequest bidRequest = givenBidRequest(); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -299,8 +300,7 @@ public void makeBidsShouldReturnVideoBidByDefaultWhenMtypeIsUnknown() throws Jso // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .extracting(BidderBid::getBid, BidderBid::getType) - .containsExactly(tuple(Bid.builder().impid("123").mtype(99).build(), video)); + .containsOnly(BidderBid.of(Bid.builder().impid("123").mtype(99).build(), video, null)); } @Test @@ -327,10 +327,10 @@ public void makeBidsShouldReturnMultipleBids() throws JsonProcessingException { tuple(Bid.builder().impid("456").mtype(2).price(BigDecimal.TEN).build(), video)); } - private static BidRequest givenBidRequest(Function impCustomizer) { + private static BidRequest givenBidRequest() { return BidRequest.builder() .site(Site.builder().page("https://test-example.com").build()) - .imp(singletonList(givenImp(impCustomizer))) + .imp(singletonList(givenImp(identity()))) .build(); } @@ -359,4 +359,3 @@ private static BidderCall givenHttpCall(BidResponse bidResponse) null); } } -