From aa718e935e55863f1030223d0075bde871a25c09 Mon Sep 17 00:00:00 2001 From: Santiago Garcia Date: Thu, 16 Apr 2026 15:33:36 +0200 Subject: [PATCH] adds ron id support for seedtag adapter --- .../server/bidder/seedtag/SeedtagBidder.java | 27 +++- .../ext/request/seedtag/ExtImpSeedtag.java | 6 + .../static/bidder-params/seedtag.json | 15 ++- .../bidder/seedtag/SeedtagBidderTest.java | 119 +++++++++++++++++- .../org/prebid/server/it/SeedtagTest.java | 17 +++ .../test-auction-seedtag-ron-request.json | 24 ++++ .../test-auction-seedtag-ron-response.json | 44 +++++++ .../seedtag/test-seedtag-ron-bid-request.json | 57 +++++++++ .../test-seedtag-ron-bid-response.json | 21 ++++ 9 files changed, 326 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-auction-seedtag-ron-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-auction-seedtag-ron-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-seedtag-ron-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-seedtag-ron-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/seedtag/SeedtagBidder.java b/src/main/java/org/prebid/server/bidder/seedtag/SeedtagBidder.java index ea15def357c..389fb88b147 100644 --- a/src/main/java/org/prebid/server/bidder/seedtag/SeedtagBidder.java +++ b/src/main/java/org/prebid/server/bidder/seedtag/SeedtagBidder.java @@ -7,6 +7,7 @@ import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; @@ -37,6 +38,7 @@ public class SeedtagBidder implements Bidder { new TypeReference<>() { }; private static final String BIDDER_CURRENCY = "USD"; + private static final String INTEGRATION_TYPE_RON_ID = "ronId"; private final String endpointUrl; private final JacksonMapper mapper; @@ -58,8 +60,8 @@ public Result>> makeHttpRequests(BidRequest request for (Imp imp : request.getImp()) { try { + validateImpExt(imp); final Price bidFloorPrice = resolveBidFloor(imp, request); - modifiedImps.add(modifyImp(imp, bidFloorPrice)); } catch (PreBidException e) { errors.add(BidderError.badInput(e.getMessage())); @@ -79,6 +81,29 @@ public Result>> makeHttpRequests(BidRequest request errors); } + private void validateImpExt(Imp imp) { + final ExtImpSeedtag ext; + try { + ext = mapper.mapper().convertValue(imp.getExt(), SEEDTAG_EXT_TYPE_REFERENCE).getBidder(); + } catch (Exception e) { + throw new PreBidException("Invalid imp.ext.bidder for imp id: %s".formatted(imp.getId())); + } + + if (INTEGRATION_TYPE_RON_ID.equals(ext.getIntegrationType())) { + if (StringUtils.isBlank(ext.getPublisherId())) { + throw new PreBidException( + "imp id %s: publisherId is required when integrationType is '%s'" + .formatted(imp.getId(), INTEGRATION_TYPE_RON_ID)); + } + } else { + if (StringUtils.isBlank(ext.getAdUnitId())) { + throw new PreBidException( + "imp id %s: adUnitId is required when integrationType is not '%s'" + .formatted(imp.getId(), INTEGRATION_TYPE_RON_ID)); + } + } + } + private static Imp modifyImp(Imp imp, Price bidFloorPrice) { return imp.toBuilder() .bidfloorcur(bidFloorPrice.getCurrency()) diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/seedtag/ExtImpSeedtag.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/seedtag/ExtImpSeedtag.java index 364b63fb7dc..57e44c16ff5 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/seedtag/ExtImpSeedtag.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/seedtag/ExtImpSeedtag.java @@ -9,4 +9,10 @@ public class ExtImpSeedtag { @JsonProperty("adUnitId") String adUnitId; + @JsonProperty("publisherId") + String publisherId; + + @JsonProperty("integrationType") + String integrationType; + } diff --git a/src/main/resources/static/bidder-params/seedtag.json b/src/main/resources/static/bidder-params/seedtag.json index 8d84b059fd0..4e09dd7eb69 100644 --- a/src/main/resources/static/bidder-params/seedtag.json +++ b/src/main/resources/static/bidder-params/seedtag.json @@ -8,9 +8,20 @@ "type": "string", "description": "Ad Unit ID", "minLength": 1 + }, + "publisherId": { + "type": "string", + "description": "Publisher ID (editorial group ID)", + "minLength": 1 + }, + "integrationType": { + "type": "string", + "description": "Integration type", + "enum": ["ronId"] } }, - "required": [ - "adUnitId" + "oneOf": [ + { "required": ["adUnitId"] }, + { "required": ["publisherId", "integrationType"] } ] } diff --git a/src/test/java/org/prebid/server/bidder/seedtag/SeedtagBidderTest.java b/src/test/java/org/prebid/server/bidder/seedtag/SeedtagBidderTest.java index a0e0fb5880b..fb8ed09f326 100644 --- a/src/test/java/org/prebid/server/bidder/seedtag/SeedtagBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/seedtag/SeedtagBidderTest.java @@ -1,6 +1,7 @@ package org.prebid.server.bidder.seedtag; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; import com.iab.openrtb.response.Bid; @@ -20,6 +21,7 @@ import org.prebid.server.bidder.model.Result; import org.prebid.server.currency.CurrencyConversionService; import org.prebid.server.exception.PreBidException; +import org.prebid.server.proto.openrtb.ext.request.seedtag.ExtImpSeedtag; import java.math.BigDecimal; import java.util.List; @@ -127,6 +129,115 @@ public void makeHttpRequestsShouldSkipImpsWithCurrencyThatCanNotBeConverted() { .hasSize(1); } + @Test + public void makeHttpRequestsShouldSucceedWithPublisherIdAndRonIdIntegrationType() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder.imp(singletonList( + givenImp(identity(), ExtImpSeedtag.of(null, "somePubId", "ronId"))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + assertThat(result.getValue().get(0).getPayload().getImp()).hasSize(1); + } + + @Test + public void makeHttpRequestsShouldSucceedWithAdUnitIdAndPublisherIdWhenIntegrationTypeIsRonId() { + // given: adUnitId is irrelevant when integrationType is ronId — publisherId is what matters + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder.imp(singletonList( + givenImp(identity(), ExtImpSeedtag.of("someAdUnitId", "somePubId", "ronId"))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + } + + @Test + public void makeHttpRequestsShouldSucceedWithAdUnitIdWhenIntegrationTypeIsAbsent() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder.imp(singletonList( + givenImp(identity(), ExtImpSeedtag.of("someAdUnitId", null, null))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + } + + @Test + public void makeHttpRequestsShouldSkipImpWithRonIdIntegrationTypeButMissingPublisherId() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder.imp(singletonList( + givenImp(identity(), ExtImpSeedtag.of("someAdUnitId", null, "ronId"))))); + + // 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()).contains("publisherId is required when integrationType is 'ronId'"); + }); + } + + @Test + public void makeHttpRequestsShouldSkipImpWithNoAdUnitIdAndNoRonIdIntegrationType() { + // given: no adUnitId and integrationType is not ronId → adUnitId is required + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder.imp(singletonList( + givenImp(identity(), ExtImpSeedtag.of(null, "somePubId", null))))); + + // 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()).contains("adUnitId is required when integrationType is not 'ronId'"); + }); + } + + @Test + public void makeHttpRequestsShouldSkipImpWithNoAdUnitIdAndNoParams() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), + requestBuilder -> requestBuilder.imp(singletonList( + givenImp(identity(), ExtImpSeedtag.of(null, null, null))))); + + // 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()).contains("adUnitId is required when integrationType is not 'ronId'"); + }); + } + @Test public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { // given @@ -231,7 +342,13 @@ private static BidRequest givenBidRequest(UnaryOperator impCusto } private static Imp givenImp(UnaryOperator impCustomizer) { - return impCustomizer.apply(Imp.builder().id("123")).build(); + return givenImp(impCustomizer, ExtImpSeedtag.of("someAdUnitId", null, null)); + } + + private static Imp givenImp(UnaryOperator impCustomizer, ExtImpSeedtag extImpSeedtag) { + final ObjectNode bidderExt = mapper.createObjectNode(); + bidderExt.set("bidder", mapper.valueToTree(extImpSeedtag)); + return impCustomizer.apply(Imp.builder().id("123").ext(bidderExt)).build(); } private static BidResponse givenBidResponse(UnaryOperator bidCustomizer) { diff --git a/src/test/java/org/prebid/server/it/SeedtagTest.java b/src/test/java/org/prebid/server/it/SeedtagTest.java index 318f2bb506d..21fb8e9d3db 100644 --- a/src/test/java/org/prebid/server/it/SeedtagTest.java +++ b/src/test/java/org/prebid/server/it/SeedtagTest.java @@ -30,4 +30,21 @@ public void openrtb2AuctionShouldRespondWithBidsFromSeedtag() throws IOException assertJsonEquals("openrtb2/seedtag/test-auction-seedtag-response.json", response, singletonList("seedtag")); } + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromSeedtagUsingPublisherIdAndRonIntegrationType() + throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/seedtag-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/seedtag/test-seedtag-ron-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/seedtag/test-seedtag-ron-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/seedtag/test-auction-seedtag-ron-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/seedtag/test-auction-seedtag-ron-response.json", response, + singletonList("seedtag")); + } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-auction-seedtag-ron-request.json b/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-auction-seedtag-ron-request.json new file mode 100644 index 00000000000..df80e6224d3 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-auction-seedtag-ron-request.json @@ -0,0 +1,24 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "seedtag": { + "publisherId": "somePubId", + "integrationType": "ronId" + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-auction-seedtag-ron-response.json b/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-auction-seedtag-ron-response.json new file mode 100644 index 00000000000..d95fbe5ae64 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-auction-seedtag-ron-response.json @@ -0,0 +1,44 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 3.33, + "adm": "adm001", + "adid": "adid001", + "cid": "cid001", + "crid": "crid001", + "w": 300, + "h": 250, + "mtype": 1, + "ext":{ + "origbidcpm":3.33, + "origbidcur":"USD", + "prebid": { + "type":"banner", + "meta": { + "adaptercode": "seedtag" + } + } + } + } + ], + "seat": "seedtag", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "seedtag": "{{ seedtag.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-seedtag-ron-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-seedtag-ron-bid-request.json new file mode 100644 index 00000000000..618fda579c7 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-seedtag-ron-bid-request.json @@ -0,0 +1,57 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "publisherId": "somePubId", + "integrationType": "ronId" + } + } + } + ], + "source": { + "tid": "${json-unit.any-string}" + }, + "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" + ], + "regs": { + "ext": { + "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/seedtag/test-seedtag-ron-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-seedtag-ron-bid-response.json new file mode 100644 index 00000000000..1992d34c094 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/seedtag/test-seedtag-ron-bid-response.json @@ -0,0 +1,21 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 3.33, + "adid": "adid001", + "crid": "crid001", + "cid": "cid001", + "adm": "adm001", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] +}