From 4393e44011d7f532ef6daaf63b33da5ff6376fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ko=C5=82ody=C5=84ski?= Date: Wed, 13 Aug 2025 07:00:20 +0200 Subject: [PATCH 1/4] SSPBC Adapter: update adapter --- .../server/bidder/sspbc/SspbcBidder.java | 257 ++------------ .../server/bidder/sspbc/SspbcRequest.java | 12 + .../sspbc/request/SspbcRequestType.java | 18 - src/main/resources/bidder-config/sspbc.yaml | 2 +- .../server/bidder/sspbc/SspbcBidderTest.java | 328 ++---------------- .../sspbc/test-auction-sspbc-request.json | 8 +- .../sspbc/test-auction-sspbc-response.json | 3 +- .../sspbc/test-sspbc-bid-request.json | 110 +++--- .../sspbc/test-sspbc-bid-response.json | 1 + 9 files changed, 144 insertions(+), 595 deletions(-) create mode 100644 src/main/java/org/prebid/server/bidder/sspbc/SspbcRequest.java delete mode 100644 src/main/java/org/prebid/server/bidder/sspbc/request/SspbcRequestType.java diff --git a/src/main/java/org/prebid/server/bidder/sspbc/SspbcBidder.java b/src/main/java/org/prebid/server/bidder/sspbc/SspbcBidder.java index c84a857533a..e0a3abe1326 100644 --- a/src/main/java/org/prebid/server/bidder/sspbc/SspbcBidder.java +++ b/src/main/java/org/prebid/server/bidder/sspbc/SspbcBidder.java @@ -1,16 +1,9 @@ package org.prebid.server.bidder.sspbc; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Format; -import com.iab.openrtb.request.Imp; -import com.iab.openrtb.request.Site; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; -import com.iab.openrtb.response.SeatBid; +import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; @@ -20,47 +13,22 @@ 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.bidder.sspbc.request.SspbcRequestType; 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.sspbc.ExtImpSspbc; 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.util.ObjectUtil; import java.net.URISyntaxException; -import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; import java.util.function.UnaryOperator; -import java.util.stream.Collectors; -public class SspbcBidder implements Bidder { +public class SspbcBidder implements Bidder { - private static final TypeReference> SSPBC_EXT_TYPE_REFERENCE = - new TypeReference<>() { - }; - private static final String ADAPTER_VERSION = "5.8"; - private static final String IMP_FALLBACK_SIZE = "1x1"; - private static final String PREBID_SERVER_INTEGRATION_TYPE = "4"; - private static final String BANNER_TEMPLATE = """ -
"""; + private static final String ADAPTER_VERSION = "6.0"; private final String endpointUrl; private final JacksonMapper mapper; @@ -71,142 +39,29 @@ public SspbcBidder(String endpointUrl, JacksonMapper mapper) { } @Override - public Result>> makeHttpRequests(BidRequest request) { - if (request.getSite() == null) { - return Result.withError(BidderError.badInput("BidRequest.site not provided")); - } - - final Map impToExt; - - try { - impToExt = createImpToExt(request); - } catch (PreBidException e) { - return Result.withError(BidderError.badInput(e.getMessage())); - } - - final SspbcRequestType requestType = getRequestType(impToExt); - final List imps = new ArrayList<>(); - String siteId = ""; - - for (Imp imp : request.getImp()) { - final ExtImpSspbc extImpSspbc = impToExt.get(imp); - final String extImpSspbcSiteId = extImpSspbc.getSiteId(); - if (StringUtils.isNotEmpty(extImpSspbcSiteId)) { - siteId = extImpSspbcSiteId; - } - - imps.add(updateImp(imp, requestType, extImpSspbc)); - } - + public Result>> makeHttpRequests(BidRequest request) { try { - return Result.withValue(createRequest(updateBidRequest(request, imps, requestType, siteId))); + final SspbcRequest outgoingRequest = SspbcRequest.of(request); + final String uri = updateUrl(getUri(endpointUrl)); + final HttpRequest httpRequest = createHttpRequest(outgoingRequest, uri, mapper); + return Result.withValue(httpRequest); } catch (PreBidException e) { return Result.withError(BidderError.badInput(e.getMessage())); } } - private Map createImpToExt(BidRequest request) { - return request.getImp() - .stream() - .collect(Collectors.toMap(Function.identity(), this::parseImpExt)); - } - - private ExtImpSspbc parseImpExt(Imp imp) { - try { - return mapper.mapper().convertValue(imp.getExt(), SSPBC_EXT_TYPE_REFERENCE).getBidder(); - } catch (IllegalArgumentException e) { - throw new PreBidException(e.getMessage()); - } - } - - private Imp updateImp(Imp imp, SspbcRequestType requestType, ExtImpSspbc extImpSspbc) { - final String originalImpId = imp.getId(); - - return imp.toBuilder() - .id(resolveImpId(originalImpId, extImpSspbc.getId(), requestType)) - .tagid(originalImpId) - .ext(createImpExt(imp)) + public HttpRequest createHttpRequest(SspbcRequest sspbcRequest, + String endpointUrl, + JacksonMapper mapper) { + return HttpRequest.builder() + .method(HttpMethod.POST) + .uri(endpointUrl) + .headers(HttpUtil.headers()) + .impIds(BidderUtil.impIds(sspbcRequest.getBidRequest())) + .body(mapper.encodeToBytes(sspbcRequest)) + .payload(sspbcRequest) .build(); - } - - private String resolveImpId(String originalImpId, String extImpId, SspbcRequestType requestType) { - return StringUtils.isNotEmpty(extImpId) && requestType != SspbcRequestType.REQUEST_TYPE_ONE_CODE - ? extImpId - : originalImpId; - } - private ObjectNode createImpExt(Imp imp) { - return mapper.mapper().createObjectNode() - .set("data", mapper.mapper().createObjectNode() - .put("pbslot", imp.getTagid()) - .put("pbsize", getImpSize(imp))); - } - - private BidRequest updateBidRequest(BidRequest bidRequest, - List imps, - SspbcRequestType requestType, - String siteId) { - - return bidRequest.toBuilder() - .imp(imps) - .site(updateSite(bidRequest.getSite(), requestType, siteId)) - .test(updateTest(requestType, bidRequest.getTest())) - .build(); - } - - private Site updateSite(Site site, SspbcRequestType requestType, String siteId) { - return site.toBuilder() - .id(resolveSiteId(requestType, siteId)) - .domain(getUri(site.getPage()).getHost()) - .build(); - } - - private static String resolveSiteId(SspbcRequestType requestType, String siteId) { - return requestType == SspbcRequestType.REQUEST_TYPE_ONE_CODE || StringUtils.isBlank(siteId) - ? StringUtils.EMPTY - : siteId; - } - - private static Integer updateTest(SspbcRequestType requestType, Integer test) { - return requestType == SspbcRequestType.REQUEST_TYPE_TEST ? 1 : test; - } - - private String getImpSize(Imp imp) { - final List formats = ObjectUtil.getIfNotNull(imp.getBanner(), Banner::getFormat); - if (CollectionUtils.isEmpty(formats)) { - return IMP_FALLBACK_SIZE; - } - - return formats.stream() - .max(Comparator.comparing(SspbcBidder::formatToArea)) - .filter(format -> formatToArea(format) > 0) - .map(format -> String.format("%dx%d", format.getW(), format.getH())) - .orElse(IMP_FALLBACK_SIZE); - } - - private static int formatToArea(Format format) { - final Integer w = ObjectUtil.getIfNotNull(format, Format::getW); - final Integer h = ObjectUtil.getIfNotNull(format, Format::getH); - - return w != null && h != null ? w * h : 0; - } - - private SspbcRequestType getRequestType(Map impToExt) { - for (ExtImpSspbc extImpSspbc : impToExt.values()) { - if (extImpSspbc.getTest() != 0) { - return SspbcRequestType.REQUEST_TYPE_TEST; - } - - if (StringUtils.isAnyEmpty(extImpSspbc.getSiteId(), extImpSspbc.getId())) { - return SspbcRequestType.REQUEST_TYPE_ONE_CODE; - } - } - - return SspbcRequestType.REQUEST_TYPE_STANDARD; - } - - private HttpRequest createRequest(BidRequest request) { - return BidderUtil.defaultRequest(request, updateUrl(getUri(endpointUrl)), mapper); } private static URIBuilder getUri(String endpointUrl) { @@ -222,21 +77,20 @@ private static URIBuilder getUri(String endpointUrl) { private String updateUrl(URIBuilder uriBuilder) { return uriBuilder .addParameter("bdver", ADAPTER_VERSION) - .addParameter("inver", PREBID_SERVER_INTEGRATION_TYPE) .toString(); } @Override - public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); - return Result.withValues(extractBids(bidResponse, httpCall.getRequest().getPayload())); + return Result.withValues(extractBids(bidResponse)); } catch (PreBidException | DecodeException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } } - private List extractBids(BidResponse bidResponse, BidRequest bidRequest) { + private List extractBids(BidResponse bidResponse) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { return Collections.emptyList(); } @@ -246,72 +100,29 @@ private List extractBids(BidResponse bidResponse, BidRequest bidReque .map(seatBid -> CollectionUtils.emptyIfNull(seatBid.getBid()) .stream() .filter(Objects::nonNull) - .map(bid -> toBidderBid(bid, seatBid.getSeat(), bidResponse.getCur(), bidRequest))) + .map(bid -> toBidderBid(bid, bidResponse.getCur()) + )) .flatMap(UnaryOperator.identity()) .toList(); } - private BidderBid toBidderBid(Bid bid, String seat, String currency, BidRequest bidRequest) { + private BidderBid toBidderBid(Bid bid, String currency) { if (StringUtils.isEmpty(bid.getAdm())) { throw new PreBidException("Bid format is not supported"); } - final ObjectNode bidExt = bid.getExt(); - final Bid updatedBid = bid.toBuilder() - .impid(getImpTagId(bidRequest.getImp(), bid)) - .adm(createBannerAd( - bid, - stringOrNull(bidExt, "adlabel"), - stringOrNull(bidExt, "pubid"), - stringOrNull(bidExt, "siteid"), - stringOrNull(bidExt, "slotid"), - seat, - bidRequest)) - .build(); - - return BidderBid.of(updatedBid, BidType.banner, currency); + return BidderBid.of(bid, getBidType(bid), currency); } - private static String stringOrNull(ObjectNode bidExt, String property) { - return Optional.ofNullable(bidExt) - .map(ext -> ext.get(property)) - .map(JsonNode::asText) - .orElse(StringUtils.EMPTY); + private BidType getBidType(Bid bid) { + return switch (bid.getMtype()) { + case null -> throw new PreBidException("Bid mtype is required"); + case 1 -> BidType.banner; + case 2 -> BidType.video; + case 3 -> BidType.audio; + case 4 -> BidType.xNative; + default -> throw new PreBidException("unsupported MType: %s.".formatted(bid.getMtype())); + }; } - private static String getImpTagId(List imps, Bid bid) { - return imps.stream() - .filter(imp -> Objects.equals(imp.getId(), bid.getImpid())) - .map(Imp::getTagid) - .findAny() - .orElseThrow(() -> new PreBidException("imp not found")); - } - - private String createBannerAd(Bid bid, - String adlabel, - String pubid, - String siteid, - String slotid, - String seat, - BidRequest bidRequest) { - if (bid.getAdm().contains("")) { - return bid.getAdm(); - } - - final ObjectNode mcad = mapper.mapper().createObjectNode() - .put("id", bidRequest.getId()) - .put("seat", seat) - .set("seatbid", mapper.mapper() - .convertValue(SeatBid.builder().bid(Collections.singletonList(bid)).build(), JsonNode.class)); - - return BANNER_TEMPLATE - .replace("{{.SiteId}}", siteid) - .replace("{{.SlotId}}", slotid) - .replace("{{.AdLabel}}", adlabel) - .replace("{{.PubId}}", pubid) - .replace("{{.Page}}", bidRequest.getSite().getPage()) - .replace("{{.Referer}}", bidRequest.getSite().getRef()) - .replace("{{.McAd}}", mcad.toString()) - .replace("{{.Inver}}", PREBID_SERVER_INTEGRATION_TYPE); - } } diff --git a/src/main/java/org/prebid/server/bidder/sspbc/SspbcRequest.java b/src/main/java/org/prebid/server/bidder/sspbc/SspbcRequest.java new file mode 100644 index 00000000000..5e0859a6c98 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/sspbc/SspbcRequest.java @@ -0,0 +1,12 @@ +package org.prebid.server.bidder.sspbc; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.iab.openrtb.request.BidRequest; +import lombok.Value; + +@Value(staticConstructor = "of") +public class SspbcRequest { + + @JsonProperty("bidRequest") + BidRequest bidRequest; +} diff --git a/src/main/java/org/prebid/server/bidder/sspbc/request/SspbcRequestType.java b/src/main/java/org/prebid/server/bidder/sspbc/request/SspbcRequestType.java deleted file mode 100644 index b78b4abbdb3..00000000000 --- a/src/main/java/org/prebid/server/bidder/sspbc/request/SspbcRequestType.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.prebid.server.bidder.sspbc.request; - -public enum SspbcRequestType { - - REQUEST_TYPE_STANDARD(1), - REQUEST_TYPE_ONE_CODE(2), - REQUEST_TYPE_TEST(3); - - private final Integer value; - - SspbcRequestType(Integer value) { - this.value = value; - } - - public Integer getValue() { - return value; - } -} diff --git a/src/main/resources/bidder-config/sspbc.yaml b/src/main/resources/bidder-config/sspbc.yaml index 609a0204204..83bb82a3f20 100644 --- a/src/main/resources/bidder-config/sspbc.yaml +++ b/src/main/resources/bidder-config/sspbc.yaml @@ -1,6 +1,6 @@ adapters: sspbc: - endpoint: https://ssp.wp.pl/v1/bidder/prebidserver + endpoint: https://ssp.wp.pl/v2/bidder/prebidserver meta-info: maintainer-email: prebid-dev@grupawp.pl app-media-types: diff --git a/src/test/java/org/prebid/server/bidder/sspbc/SspbcBidderTest.java b/src/test/java/org/prebid/server/bidder/sspbc/SspbcBidderTest.java index e3224b99811..d51d4061b3f 100644 --- a/src/test/java/org/prebid/server/bidder/sspbc/SspbcBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/sspbc/SspbcBidderTest.java @@ -1,15 +1,12 @@ package org.prebid.server.bidder.sspbc; import com.fasterxml.jackson.core.JsonProcessingException; -import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Format; import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Site; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; -import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; import org.prebid.server.VertxTest; import org.prebid.server.bidder.model.BidderBid; @@ -18,8 +15,7 @@ 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.sspbc.ExtImpSspbc; +import org.prebid.server.proto.openrtb.ext.response.BidType; import java.util.List; import java.util.function.UnaryOperator; @@ -46,245 +42,33 @@ public void makeHttpRequestsShouldCreateExpectedUrl() { final BidRequest bidRequest = givenBidRequest(identity()); // when - final Result>> result = target.makeHttpRequests(bidRequest); + final Result>> result = target.makeHttpRequests(bidRequest); // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1) .extracting(HttpRequest::getUri) - .containsExactly("https://randomurl.com?bdver=5.8&inver=4"); + .containsExactly("https://randomurl.com?bdver=6.0"); } @Test - public void makeHttpRequestsShouldThrowErrorWhenSiteNull() { + public void makeHttpRequestsShouldCreateValidPayload() { // given - final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder.site(null), identity()); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).hasSize(1) - .allSatisfy(error -> { - assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); - assertThat(error.getMessage()).startsWith("BidRequest.site not provided"); - }); - } - - @Test - public void makeHttpRequestsShouldReturnErrorsOfNotValidExtImps() { - // 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) - .allSatisfy(error -> { - assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); - assertThat(error.getMessage()).startsWith("Cannot deserialize value of type"); - }); - } - - @Test - public void makeHttpRequestsShouldUpdateBidRequestTestTo1WhenBidRequestTest3AndImpExtTestIsNotEmpty() { - // given - final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder.test(3), - identity()); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getTest) - .containsExactly(1); - } - - @Test - public void makeHttpRequestsShouldUpdateSiteIdToEmptyStringWhenImpExtTestZeroAndImpExtSiteIdNotEmpty() { - // given - final BidRequest bidRequest = givenBidRequest( - bidRequestBuilder -> bidRequestBuilder - .site(Site.builder() - .id("AnySiteId") - .page("https://test.page/") - .build()), - impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSspbc - .of("extSiteId", null, 0))))); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getSite) - .extracting(Site::getId) - .containsExactly(StringUtils.EMPTY); - } - - @Test - public void makeHttpRequestsShouldUpdateSiteIdToEmptyStringWhenImpExtTestZeroAndImpExtIdNotEmpty() { - // given - final BidRequest bidRequest = givenBidRequest( - bidRequestBuilder -> bidRequestBuilder - .site(Site.builder() - .id("AnySiteId") - .page("https://test.page/") - .build()), - impBuilder -> impBuilder.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSspbc - .of(null, "ExtImpId", 0))))); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getSite) - .extracting(Site::getId) - .containsExactly(StringUtils.EMPTY); - } - - @Test - public void makeHttpRequestsShouldReplaceSiteIdFromImpExtSiteIdWhenImpExtTestZeroAndImpExtIdAndSiteIdNotEmpty() { - // given - final String expectedSiteId = "siteId"; - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSspbc - .of(expectedSiteId, "ExtImpId", 0))))); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .extracting(BidRequest::getSite) - .extracting(Site::getId) - .containsExactly(expectedSiteId); - } - - @Test - public void makeHttpRequestsShouldReplaceImpIdOnImpExtIdWhenImpExtSiteAndIdIsNotEmpty() { - // given - final String expectedId = "AnyId"; - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.id("randomImpId") - .ext(mapper.valueToTree( - ExtPrebid.of(null, ExtImpSspbc.of("SiteId", expectedId, 1))))); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getId) - .containsExactly(expectedId); - } - - @Test - public void makeHttpRequestsShouldReplaceImpTagIdOnImpIdWhenImpExtIdNull() { - // given - final String expectedId = "impId"; - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder - .id(expectedId) - .tagid("randomTagId") - .ext(mapper.valueToTree(ExtPrebid - .of(null, ExtImpSspbc.of("siteId", null, 123))))); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getTagid) - .containsExactly(expectedId); - } - - @Test - public void makeHttpRequestsShouldUpdateImpExtWithData() { - // given - final String expectedPbSlot = "randomTagId"; - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder - .tagid(expectedPbSlot)); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getExt) - .containsExactly(mapper.createObjectNode() - .set("data", mapper.createObjectNode() - .put("pbslot", expectedPbSlot) - .put("pbsize", "1x1"))); - } - - @Test - public void makeHttpRequestsShouldUpdateImpExtDataPbSizeWithBannerFormatWandH() { - // given - final String expectedPbSlot = "randomTagId"; - final Integer bannerFormatW = 25; - final Integer bannerFormatH = 45; - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder - .banner(Banner.builder() - .format(singletonList(Format.builder() - .w(bannerFormatW) - .h(bannerFormatH).build())).build()) - .tagid(expectedPbSlot)); + final BidRequest bidRequest = givenBidRequest(identity()); - // when - final Result>> result = target.makeHttpRequests(bidRequest); + //when + final Result>> result = target.makeHttpRequests(bidRequest); // then assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getPayload) - .flatExtracting(BidRequest::getImp) - .extracting(Imp::getExt) - .containsExactly(mapper.createObjectNode() - .set("data", mapper.createObjectNode() - .put("pbslot", expectedPbSlot) - .put("pbsize", String.format("%dx%d", bannerFormatW, bannerFormatH)))); - } - - @Test - public void makeHttpRequestsShouldReturnErrorWhenSitePageIsInvalid() { - // given - final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder - .site(Site.builder().page("invalid_url////[' and ']").build()), identity()); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).hasSize(1) - .containsExactly(BidderError.badInput("Malformed URL: invalid_url////[' and '].")); + assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue().getFirst().getPayload()).isEqualTo(SspbcRequest.of(bidRequest)); } @Test public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { // given - final BidderCall httpCall = givenHttpCall(null, "invalid"); + final BidderCall httpCall = givenHttpCall(null, "invalid"); // when final Result> result = target.makeBids(httpCall, null); @@ -301,7 +85,7 @@ public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { @Test public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null)); + final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(null)); // when final Result> result = target.makeBids(httpCall, null); @@ -312,34 +96,14 @@ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProces } @Test - public void makeBidsShouldReturnErrorWhenImpIdNotEqualsBidImpId() throws JsonProcessingException { - // given - final BidderCall httpCall = givenHttpCall(givenBidRequest(identity()), - mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.adm("Any adm")))); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()) - .containsExactly(BidderError.badServerResponse("imp not found")); - } - - @Test - public void makeBidsShouldReturnErrorWhenBidAmdIsNull() throws JsonProcessingException { + public void makeBidsShouldReturnErrorWhenAdmIsEmpty() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidRequest(identity(), + final BidderCall httpCall = givenHttpCall(givenBidRequest(identity(), impBuilder -> impBuilder.id("id").tagid("tagId")), mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder .impid("id") - .adm(null) - .ext(mapper.createObjectNode() - .put("adlabel", "anyAdLabel") - .put("pubid", "anyPubId") - .put("siteid", "anySiteId") - .put("slotid", "anySlotId"))))); + .adm(null)))); // when final Result> result = target.makeBids(httpCall, null); @@ -351,40 +115,29 @@ public void makeBidsShouldReturnErrorWhenBidAmdIsNull() throws JsonProcessingExc } @Test - public void makeBidsShouldNotUpdateBidAdmWhenBidAdmContainPreformattedValue() throws JsonProcessingException { + public void makeBidsShouldReturnErrorWhenMTypeIsIncorrect() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidRequest( - bidRequestBuilder -> bidRequestBuilder.site( - Site.builder() - .page("AnyPage") - .ref("anyRef") - .build()), + final BidderCall httpCall = givenHttpCall(givenBidRequest(identity(), impBuilder -> impBuilder.id("id").tagid("tagId")), mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder .impid("id") - .adm("any") - .ext(mapper.createObjectNode() - .put("adlabel", "anyAdLabel") - .put("pubid", "anyPubId") - .put("siteid", "anySiteId") - .put("slotid", "anySlotId"))))); + .adm("anyAdm") + .mtype(100)))); // when final Result> result = target.makeBids(httpCall, null); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(BidderBid::getBid) - .extracting(Bid::getAdm) - .containsExactly("any"); + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()) + .containsExactly(BidderError.badServerResponse("unsupported MType: 100.")); } @Test - public void makeBidsShouldNotUpdateBidAdmWhenBidAdmNotContainPreformattedValue() throws JsonProcessingException { + public void makeBidsShouldParseBid() throws JsonProcessingException { // given - final BidderCall httpCall = givenHttpCall(givenBidRequest( + final BidderCall httpCall = givenHttpCall(givenBidRequest( bidRequestBuilder -> bidRequestBuilder .id("bidRequestId") .site(Site.builder() @@ -396,11 +149,7 @@ public void makeBidsShouldNotUpdateBidAdmWhenBidAdmNotContainPreformattedValue() bidBuilder .impid("id") .adm("anyAdm") - .ext(mapper.createObjectNode() - .put("adlabel", "anyAdLabel") - .put("pubid", "anyPubId") - .put("siteid", "anySiteId") - .put("slotid", "anySlotId"))))); + .mtype(1)))); // when final Result> result = target.makeBids(httpCall, null); @@ -410,18 +159,14 @@ public void makeBidsShouldNotUpdateBidAdmWhenBidAdmNotContainPreformattedValue() assertThat(result.getValue()) .extracting(BidderBid::getBid) .extracting(Bid::getAdm) - .containsExactly("" - + "
"); + .containsExactly("anyAdm"); + assertThat(result.getValue()) + .extracting(BidderBid::getBid) + .extracting(Bid::getMtype) + .containsExactly(1); + assertThat(result.getValue()) + .extracting(BidderBid::getType) + .containsExactly(BidType.banner); } private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { @@ -434,10 +179,7 @@ private static BidRequest givenBidRequest( return bidRequestCustomizer.apply(BidRequest.builder() .site(Site.builder().page("https://test.page/").build()) .test(0) - .imp(singletonList(impCustomizer.apply(Imp.builder().id("123") - .ext(mapper.valueToTree(ExtPrebid - .of(null, ExtImpSspbc - .of("siteId", "AnyId", 123))))).build()))) + .imp(singletonList(impCustomizer.apply(Imp.builder().id("123")).build()))) .build(); } @@ -449,9 +191,9 @@ private static BidResponse givenBidResponse(UnaryOperator bidCus .build(); } - private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { + private static BidderCall givenHttpCall(BidRequest bidRequest, String body) { return BidderCall.succeededHttp( - HttpRequest.builder().payload(bidRequest).build(), + HttpRequest.builder().payload(SspbcRequest.of(bidRequest)).build(), HttpResponse.of(200, null, body), null); } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-auction-sspbc-request.json b/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-auction-sspbc-request.json index d6d4d2ad15a..936226b3fa3 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-auction-sspbc-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-auction-sspbc-request.json @@ -2,8 +2,8 @@ "id": "request_id", "imp": [ { - "id": "imp_id", - "tagid": "imp_id", + "id": "anyId", + "tagid": "tagid", "banner": { "format": [ { @@ -25,8 +25,6 @@ ], "tmax": 5000, "regs": { - "ext": { - "gdpr": 0 - } + "gdpr": 0 } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-auction-sspbc-response.json b/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-auction-sspbc-response.json index fd673b34ff5..1c0db419be0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-auction-sspbc-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-auction-sspbc-response.json @@ -5,7 +5,7 @@ "bid": [ { "id": "bid_id", - "impid": "imp_id", + "impid": "anyId", "exp": 300, "price": 3.33, "adm": "", @@ -14,6 +14,7 @@ "crid": "crid001", "w": 300, "h": 250, + "mtype": 1, "ext": { "adlabel": "Reklama", "pubid": "431", diff --git a/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-sspbc-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-sspbc-bid-request.json index 45a2b5ac8d9..b23e02658dd 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-sspbc-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-sspbc-bid-request.json @@ -1,64 +1,66 @@ { - "id": "request_id", - "imp": [ - { - "id": "anyId", - "banner": { - "format": [ - { - "w": 120, - "h": 600 + "bidRequest" : { + "id": "request_id", + "imp": [ + { + "id": "anyId", + "banner": { + "format": [ + { + "w": 120, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "tagid": "tagid", + "secure" : 1, + "ext": { + "tid" : "${json-unit.any-string}", + "bidder": { + "siteId": "siteId", + "id": "anyId", + "test": 1 } - ], - "w": 300, - "h": 250 + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" }, - "tagid": "imp_id", - "secure" : 1, "ext": { - "data": { - "pbslot": "imp_id", - "pbsize": "120x600" - } + "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}" + }, + "regs": { + "ext": { + "gdpr": 0 } - } - ], - "site": { - "id" : "siteId", - "domain": "www.example.com", - "page": "http://www.example.com", - "publisher": { - "domain": "example.com" }, "ext": { - "amp": 0 - } - }, - "device": { - "ua": "userAgent", - "ip": "193.168.244.1" - }, - "source": { - "tid": "${json-unit.any-string}" - }, - "test": 1, - "at": 1, - "tmax": "${json-unit.any-number}", - "cur": [ - "USD" - ], - "regs": { - "ext": { - "gdpr": 0 - } - }, - "ext": { - "prebid": { - "server": { - "externalurl": "http://localhost:8080", - "gvlid": 1, - "datacenter": "local", - "endpoint": "/openrtb2/auction" + "prebid": { + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } } } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-sspbc-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-sspbc-bid-response.json index f306fddd03c..72a2e316cd0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-sspbc-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/sspbc/test-sspbc-bid-response.json @@ -13,6 +13,7 @@ "adm": "", "h": 250, "w": 300, + "mtype": 1, "ext": { "adlabel": "Reklama", "pubid": "431", From c4cc4a67f0f4cbcef207c747fd2333da6fd15987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ko=C5=82ody=C5=84ski?= Date: Mon, 18 Aug 2025 10:35:56 +0200 Subject: [PATCH 2/4] SSPBC Adapter: updates for CR sugestion --- .../server/bidder/sspbc/SspbcBidder.java | 66 ++++++------------- .../server/bidder/sspbc/SspbcBidderTest.java | 2 +- 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/sspbc/SspbcBidder.java b/src/main/java/org/prebid/server/bidder/sspbc/SspbcBidder.java index e0a3abe1326..47419fb21a7 100644 --- a/src/main/java/org/prebid/server/bidder/sspbc/SspbcBidder.java +++ b/src/main/java/org/prebid/server/bidder/sspbc/SspbcBidder.java @@ -3,9 +3,9 @@ import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.response.Bid; import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; @@ -21,10 +21,10 @@ import org.prebid.server.util.HttpUtil; import java.net.URISyntaxException; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.function.UnaryOperator; public class SspbcBidder implements Bidder { @@ -40,44 +40,30 @@ public SspbcBidder(String endpointUrl, JacksonMapper mapper) { @Override public Result>> makeHttpRequests(BidRequest request) { - try { - final SspbcRequest outgoingRequest = SspbcRequest.of(request); - final String uri = updateUrl(getUri(endpointUrl)); - final HttpRequest httpRequest = createHttpRequest(outgoingRequest, uri, mapper); - return Result.withValue(httpRequest); - } catch (PreBidException e) { - return Result.withError(BidderError.badInput(e.getMessage())); - } + return Result.withValue(createHttpRequest(request)); } - public HttpRequest createHttpRequest(SspbcRequest sspbcRequest, - String endpointUrl, - JacksonMapper mapper) { + private HttpRequest createHttpRequest(BidRequest request) { + final SspbcRequest outgoingRequest = SspbcRequest.of(request); return HttpRequest.builder() .method(HttpMethod.POST) - .uri(endpointUrl) + .uri(makeUrl(endpointUrl)) .headers(HttpUtil.headers()) - .impIds(BidderUtil.impIds(sspbcRequest.getBidRequest())) - .body(mapper.encodeToBytes(sspbcRequest)) - .payload(sspbcRequest) + .impIds(BidderUtil.impIds(outgoingRequest.getBidRequest())) + .body(mapper.encodeToBytes(outgoingRequest)) + .payload(outgoingRequest) .build(); - } - private static URIBuilder getUri(String endpointUrl) { - final URIBuilder uri; + private static String makeUrl(String endpointUrl) { try { - uri = new URIBuilder(endpointUrl); + return new URIBuilder(endpointUrl) + .addParameter("bdver", ADAPTER_VERSION) + .build() + .toString(); } catch (URISyntaxException e) { throw new PreBidException("Malformed URL: %s.".formatted(endpointUrl)); } - return uri; - } - - private String updateUrl(URIBuilder uriBuilder) { - return uriBuilder - .addParameter("bdver", ADAPTER_VERSION) - .toString(); } @Override @@ -97,32 +83,22 @@ private List extractBids(BidResponse bidResponse) { return bidResponse.getSeatbid().stream() .filter(Objects::nonNull) - .map(seatBid -> CollectionUtils.emptyIfNull(seatBid.getBid()) - .stream() - .filter(Objects::nonNull) - .map(bid -> toBidderBid(bid, bidResponse.getCur()) - )) - .flatMap(UnaryOperator.identity()) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(Objects::nonNull) + .map(bid -> BidderBid.of(bid, getBidType(bid), bidResponse.getCur())) .toList(); } - private BidderBid toBidderBid(Bid bid, String currency) { - if (StringUtils.isEmpty(bid.getAdm())) { - throw new PreBidException("Bid format is not supported"); - } - - return BidderBid.of(bid, getBidType(bid), currency); - } - - private BidType getBidType(Bid bid) { + private static BidType getBidType(Bid bid) { return switch (bid.getMtype()) { - case null -> throw new PreBidException("Bid mtype is required"); case 1 -> BidType.banner; case 2 -> BidType.video; case 3 -> BidType.audio; case 4 -> BidType.xNative; + case null -> throw new PreBidException("Bid mtype is required"); default -> throw new PreBidException("unsupported MType: %s.".formatted(bid.getMtype())); }; } - } diff --git a/src/test/java/org/prebid/server/bidder/sspbc/SspbcBidderTest.java b/src/test/java/org/prebid/server/bidder/sspbc/SspbcBidderTest.java index d51d4061b3f..94e77d0355a 100644 --- a/src/test/java/org/prebid/server/bidder/sspbc/SspbcBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/sspbc/SspbcBidderTest.java @@ -111,7 +111,7 @@ public void makeBidsShouldReturnErrorWhenAdmIsEmpty() throws JsonProcessingExcep // then assertThat(result.getValue()).isEmpty(); assertThat(result.getErrors()) - .containsExactly(BidderError.badServerResponse("Bid format is not supported")); + .containsExactly(BidderError.badServerResponse("Bid mtype is required")); } @Test From 04bbb7b8610aa64f18f1028aa56c92c22f162d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ko=C5=82ody=C5=84ski?= Date: Mon, 25 Aug 2025 07:08:20 +0200 Subject: [PATCH 3/4] CR: update bidder redirect config --- src/main/resources/bidder-config/sspbc.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/bidder-config/sspbc.yaml b/src/main/resources/bidder-config/sspbc.yaml index 83bb82a3f20..f9c5a30dd13 100644 --- a/src/main/resources/bidder-config/sspbc.yaml +++ b/src/main/resources/bidder-config/sspbc.yaml @@ -14,3 +14,7 @@ adapters: url: https://ssp.wp.pl/bidder/usersync?tcf=2&redirect={{redirect_url}} support-cors: false uid-macro: '$UID' + redirect: + url: https://ssp.wp.pl/v1/sync/prebid-server/pixel?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&redirect={{redirect_url}} + userMacro: $UID + support-cors: false \ No newline at end of file From e5e861cc46556ec0803282f59c29ea3514e0c1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ko=C5=82ody=C5=84ski?= Date: Fri, 29 Aug 2025 09:12:32 +0200 Subject: [PATCH 4/4] CR: fix uid-macro param naming --- src/main/resources/bidder-config/sspbc.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/bidder-config/sspbc.yaml b/src/main/resources/bidder-config/sspbc.yaml index f9c5a30dd13..e81c64f7069 100644 --- a/src/main/resources/bidder-config/sspbc.yaml +++ b/src/main/resources/bidder-config/sspbc.yaml @@ -16,5 +16,5 @@ adapters: uid-macro: '$UID' redirect: url: https://ssp.wp.pl/v1/sync/prebid-server/pixel?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&redirect={{redirect_url}} - userMacro: $UID - support-cors: false \ No newline at end of file + support-cors: false + uid-macro: '$UID' \ No newline at end of file