Skip to content

Commit c4e01b7

Browse files
committed
adds ron id support for seedtag adapter
1 parent 69b1993 commit c4e01b7

9 files changed

Lines changed: 326 additions & 4 deletions

File tree

src/main/java/org/prebid/server/bidder/seedtag/SeedtagBidder.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.iab.openrtb.response.BidResponse;
88
import com.iab.openrtb.response.SeatBid;
99
import org.apache.commons.collections4.CollectionUtils;
10+
import org.apache.commons.lang3.StringUtils;
1011
import org.prebid.server.bidder.Bidder;
1112
import org.prebid.server.bidder.model.BidderBid;
1213
import org.prebid.server.bidder.model.BidderCall;
@@ -37,6 +38,7 @@ public class SeedtagBidder implements Bidder<BidRequest> {
3738
new TypeReference<>() {
3839
};
3940
private static final String BIDDER_CURRENCY = "USD";
41+
private static final String INTEGRATION_TYPE_RON_ID = "ronId";
4042

4143
private final String endpointUrl;
4244
private final JacksonMapper mapper;
@@ -58,8 +60,8 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request
5860

5961
for (Imp imp : request.getImp()) {
6062
try {
63+
validateImpExt(imp);
6164
final Price bidFloorPrice = resolveBidFloor(imp, request);
62-
6365
modifiedImps.add(modifyImp(imp, bidFloorPrice));
6466
} catch (PreBidException e) {
6567
errors.add(BidderError.badInput(e.getMessage()));
@@ -79,6 +81,29 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request
7981
errors);
8082
}
8183

84+
private void validateImpExt(Imp imp) {
85+
final ExtImpSeedtag ext;
86+
try {
87+
ext = mapper.mapper().convertValue(imp.getExt(), SEEDTAG_EXT_TYPE_REFERENCE).getBidder();
88+
} catch (Exception e) {
89+
throw new PreBidException("Invalid imp.ext.bidder for imp id: %s".formatted(imp.getId()));
90+
}
91+
92+
if (INTEGRATION_TYPE_RON_ID.equals(ext.getIntegrationType())) {
93+
if (StringUtils.isBlank(ext.getPublisherId())) {
94+
throw new PreBidException(
95+
"imp id %s: publisherId is required when integrationType is '%s'"
96+
.formatted(imp.getId(), INTEGRATION_TYPE_RON_ID));
97+
}
98+
} else {
99+
if (StringUtils.isBlank(ext.getAdUnitId())) {
100+
throw new PreBidException(
101+
"imp id %s: adUnitId is required when integrationType is not '%s'"
102+
.formatted(imp.getId(), INTEGRATION_TYPE_RON_ID));
103+
}
104+
}
105+
}
106+
82107
private static Imp modifyImp(Imp imp, Price bidFloorPrice) {
83108
return imp.toBuilder()
84109
.bidfloorcur(bidFloorPrice.getCurrency())

src/main/java/org/prebid/server/proto/openrtb/ext/request/seedtag/ExtImpSeedtag.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,10 @@ public class ExtImpSeedtag {
99
@JsonProperty("adUnitId")
1010
String adUnitId;
1111

12+
@JsonProperty("publisherId")
13+
String publisherId;
14+
15+
@JsonProperty("integrationType")
16+
String integrationType;
17+
1218
}

src/main/resources/static/bidder-params/seedtag.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,20 @@
88
"type": "string",
99
"description": "Ad Unit ID",
1010
"minLength": 1
11+
},
12+
"publisherId": {
13+
"type": "string",
14+
"description": "Publisher ID (editorial group ID)",
15+
"minLength": 1
16+
},
17+
"integrationType": {
18+
"type": "string",
19+
"description": "Integration type",
20+
"enum": ["ronId"]
1121
}
1222
},
13-
"required": [
14-
"adUnitId"
23+
"oneOf": [
24+
{ "required": ["adUnitId"] },
25+
{ "required": ["publisherId", "integrationType"] }
1526
]
1627
}

src/test/java/org/prebid/server/bidder/seedtag/SeedtagBidderTest.java

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.prebid.server.bidder.seedtag;
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
45
import com.iab.openrtb.request.BidRequest;
56
import com.iab.openrtb.request.Imp;
67
import com.iab.openrtb.response.Bid;
@@ -20,6 +21,7 @@
2021
import org.prebid.server.bidder.model.Result;
2122
import org.prebid.server.currency.CurrencyConversionService;
2223
import org.prebid.server.exception.PreBidException;
24+
import org.prebid.server.proto.openrtb.ext.request.seedtag.ExtImpSeedtag;
2325

2426
import java.math.BigDecimal;
2527
import java.util.List;
@@ -127,6 +129,115 @@ public void makeHttpRequestsShouldSkipImpsWithCurrencyThatCanNotBeConverted() {
127129
.hasSize(1);
128130
}
129131

132+
@Test
133+
public void makeHttpRequestsShouldSucceedWithPublisherIdAndRonIdIntegrationType() {
134+
// given
135+
final BidRequest bidRequest = givenBidRequest(
136+
identity(),
137+
requestBuilder -> requestBuilder.imp(singletonList(
138+
givenImp(identity(), ExtImpSeedtag.of(null, "somePubId", "ronId")))));
139+
140+
// when
141+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
142+
143+
// then
144+
assertThat(result.getErrors()).isEmpty();
145+
assertThat(result.getValue()).hasSize(1);
146+
assertThat(result.getValue().get(0).getPayload().getImp()).hasSize(1);
147+
}
148+
149+
@Test
150+
public void makeHttpRequestsShouldSucceedWithAdUnitIdAndPublisherIdWhenIntegrationTypeIsRonId() {
151+
// given: adUnitId is irrelevant when integrationType is ronId — publisherId is what matters
152+
final BidRequest bidRequest = givenBidRequest(
153+
identity(),
154+
requestBuilder -> requestBuilder.imp(singletonList(
155+
givenImp(identity(), ExtImpSeedtag.of("someAdUnitId", "somePubId", "ronId")))));
156+
157+
// when
158+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
159+
160+
// then
161+
assertThat(result.getErrors()).isEmpty();
162+
assertThat(result.getValue()).hasSize(1);
163+
}
164+
165+
@Test
166+
public void makeHttpRequestsShouldSucceedWithAdUnitIdWhenIntegrationTypeIsAbsent() {
167+
// given
168+
final BidRequest bidRequest = givenBidRequest(
169+
identity(),
170+
requestBuilder -> requestBuilder.imp(singletonList(
171+
givenImp(identity(), ExtImpSeedtag.of("someAdUnitId", null, null)))));
172+
173+
// when
174+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
175+
176+
// then
177+
assertThat(result.getErrors()).isEmpty();
178+
assertThat(result.getValue()).hasSize(1);
179+
}
180+
181+
@Test
182+
public void makeHttpRequestsShouldSkipImpWithRonIdIntegrationTypeButMissingPublisherId() {
183+
// given
184+
final BidRequest bidRequest = givenBidRequest(
185+
identity(),
186+
requestBuilder -> requestBuilder.imp(singletonList(
187+
givenImp(identity(), ExtImpSeedtag.of("someAdUnitId", null, "ronId")))));
188+
189+
// when
190+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
191+
192+
// then
193+
assertThat(result.getValue()).isEmpty();
194+
assertThat(result.getErrors()).hasSize(1)
195+
.allSatisfy(error -> {
196+
assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input);
197+
assertThat(error.getMessage()).contains("publisherId is required when integrationType is 'ronId'");
198+
});
199+
}
200+
201+
@Test
202+
public void makeHttpRequestsShouldSkipImpWithNoAdUnitIdAndNoRonIdIntegrationType() {
203+
// given: no adUnitId and integrationType is not ronId → adUnitId is required
204+
final BidRequest bidRequest = givenBidRequest(
205+
identity(),
206+
requestBuilder -> requestBuilder.imp(singletonList(
207+
givenImp(identity(), ExtImpSeedtag.of(null, "somePubId", null)))));
208+
209+
// when
210+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
211+
212+
// then
213+
assertThat(result.getValue()).isEmpty();
214+
assertThat(result.getErrors()).hasSize(1)
215+
.allSatisfy(error -> {
216+
assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input);
217+
assertThat(error.getMessage()).contains("adUnitId is required when integrationType is not 'ronId'");
218+
});
219+
}
220+
221+
@Test
222+
public void makeHttpRequestsShouldSkipImpWithNoAdUnitIdAndNoParams() {
223+
// given
224+
final BidRequest bidRequest = givenBidRequest(
225+
identity(),
226+
requestBuilder -> requestBuilder.imp(singletonList(
227+
givenImp(identity(), ExtImpSeedtag.of(null, null, null)))));
228+
229+
// when
230+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
231+
232+
// then
233+
assertThat(result.getValue()).isEmpty();
234+
assertThat(result.getErrors()).hasSize(1)
235+
.allSatisfy(error -> {
236+
assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input);
237+
assertThat(error.getMessage()).contains("adUnitId is required when integrationType is not 'ronId'");
238+
});
239+
}
240+
130241
@Test
131242
public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException {
132243
// given
@@ -231,7 +342,13 @@ private static BidRequest givenBidRequest(UnaryOperator<Imp.ImpBuilder> impCusto
231342
}
232343

233344
private static Imp givenImp(UnaryOperator<Imp.ImpBuilder> impCustomizer) {
234-
return impCustomizer.apply(Imp.builder().id("123")).build();
345+
return givenImp(impCustomizer, ExtImpSeedtag.of("someAdUnitId", null, null));
346+
}
347+
348+
private static Imp givenImp(UnaryOperator<Imp.ImpBuilder> impCustomizer, ExtImpSeedtag extImpSeedtag) {
349+
final ObjectNode bidderExt = mapper.createObjectNode();
350+
bidderExt.set("bidder", mapper.valueToTree(extImpSeedtag));
351+
return impCustomizer.apply(Imp.builder().id("123").ext(bidderExt)).build();
235352
}
236353

237354
private static BidResponse givenBidResponse(UnaryOperator<Bid.BidBuilder> bidCustomizer) {

src/test/java/org/prebid/server/it/SeedtagTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,21 @@ public void openrtb2AuctionShouldRespondWithBidsFromSeedtag() throws IOException
3030
assertJsonEquals("openrtb2/seedtag/test-auction-seedtag-response.json", response,
3131
singletonList("seedtag"));
3232
}
33+
34+
@Test
35+
public void openrtb2AuctionShouldRespondWithBidsFromSeedtagUsingPublisherIdAndRonIntegrationType()
36+
throws IOException, JSONException {
37+
// given
38+
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/seedtag-exchange"))
39+
.withRequestBody(equalToJson(jsonFrom("openrtb2/seedtag/test-seedtag-ron-bid-request.json")))
40+
.willReturn(aResponse().withBody(jsonFrom("openrtb2/seedtag/test-seedtag-ron-bid-response.json"))));
41+
42+
// when
43+
final Response response = responseFor("openrtb2/seedtag/test-auction-seedtag-ron-request.json",
44+
Endpoint.openrtb2_auction);
45+
46+
// then
47+
assertJsonEquals("openrtb2/seedtag/test-auction-seedtag-ron-response.json", response,
48+
singletonList("seedtag"));
49+
}
3350
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"id": "request_id",
3+
"imp": [
4+
{
5+
"id": "imp_id",
6+
"banner": {
7+
"w": 300,
8+
"h": 250
9+
},
10+
"ext": {
11+
"seedtag": {
12+
"publisherId": "somePubId",
13+
"integrationType": "ronId"
14+
}
15+
}
16+
}
17+
],
18+
"tmax": 5000,
19+
"regs": {
20+
"ext": {
21+
"gdpr": 0
22+
}
23+
}
24+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"id": "request_id",
3+
"seatbid": [
4+
{
5+
"bid": [
6+
{
7+
"id": "bid_id",
8+
"impid": "imp_id",
9+
"exp": 300,
10+
"price": 3.33,
11+
"adm": "adm001",
12+
"adid": "adid001",
13+
"cid": "cid001",
14+
"crid": "crid001",
15+
"w": 300,
16+
"h": 250,
17+
"mtype": 1,
18+
"ext":{
19+
"origbidcpm":3.33,
20+
"origbidcur":"USD",
21+
"prebid": {
22+
"type":"banner",
23+
"meta": {
24+
"adaptercode": "seedtag"
25+
}
26+
}
27+
}
28+
}
29+
],
30+
"seat": "seedtag",
31+
"group": 0
32+
}
33+
],
34+
"cur": "USD",
35+
"ext": {
36+
"responsetimemillis": {
37+
"seedtag": "{{ seedtag.response_time_ms }}"
38+
},
39+
"prebid": {
40+
"auctiontimestamp": 0
41+
},
42+
"tmaxrequest": 5000
43+
}
44+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"id": "request_id",
3+
"imp": [
4+
{
5+
"id": "imp_id",
6+
"secure": 1,
7+
"banner": {
8+
"w": 300,
9+
"h": 250
10+
},
11+
"ext": {
12+
"tid": "${json-unit.any-string}",
13+
"bidder": {
14+
"publisherId": "somePubId",
15+
"integrationType": "ronId"
16+
}
17+
}
18+
}
19+
],
20+
"source": {
21+
"tid": "${json-unit.any-string}"
22+
},
23+
"site": {
24+
"domain": "www.example.com",
25+
"page": "http://www.example.com",
26+
"publisher": {
27+
"domain": "example.com"
28+
},
29+
"ext": {
30+
"amp": 0
31+
}
32+
},
33+
"device": {
34+
"ua": "userAgent",
35+
"ip": "193.168.244.1"
36+
},
37+
"at": 1,
38+
"tmax": "${json-unit.any-number}",
39+
"cur": [
40+
"USD"
41+
],
42+
"regs": {
43+
"ext": {
44+
"gdpr": 0
45+
}
46+
},
47+
"ext": {
48+
"prebid": {
49+
"server": {
50+
"externalurl": "http://localhost:8080",
51+
"gvlid": 1,
52+
"datacenter": "local",
53+
"endpoint": "/openrtb2/auction"
54+
}
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)