Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -37,6 +38,7 @@ public class SeedtagBidder implements Bidder<BidRequest> {
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;
Expand All @@ -58,8 +60,8 @@ public Result<List<HttpRequest<BidRequest>>> 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()));
Expand All @@ -79,6 +81,29 @@ public Result<List<HttpRequest<BidRequest>>> 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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ public class ExtImpSeedtag {
@JsonProperty("adUnitId")
String adUnitId;

@JsonProperty("publisherId")
String publisherId;

@JsonProperty("integrationType")
String integrationType;

}
15 changes: 13 additions & 2 deletions src/main/resources/static/bidder-params/seedtag.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
]
}
119 changes: 118 additions & 1 deletion src/test/java/org/prebid/server/bidder/seedtag/SeedtagBidderTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<List<HttpRequest<BidRequest>>> 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<List<HttpRequest<BidRequest>>> 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<List<HttpRequest<BidRequest>>> 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<List<HttpRequest<BidRequest>>> 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<List<HttpRequest<BidRequest>>> 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<List<HttpRequest<BidRequest>>> 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
Expand Down Expand Up @@ -231,7 +342,13 @@ private static BidRequest givenBidRequest(UnaryOperator<Imp.ImpBuilder> impCusto
}

private static Imp givenImp(UnaryOperator<Imp.ImpBuilder> impCustomizer) {
return impCustomizer.apply(Imp.builder().id("123")).build();
return givenImp(impCustomizer, ExtImpSeedtag.of("someAdUnitId", null, null));
}

private static Imp givenImp(UnaryOperator<Imp.ImpBuilder> 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<Bid.BidBuilder> bidCustomizer) {
Expand Down
17 changes: 17 additions & 0 deletions src/test/java/org/prebid/server/it/SeedtagTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
Loading