Skip to content

Commit 55d283e

Browse files
Adjusted according to review feedback
1 parent ebf5313 commit 55d283e

12 files changed

Lines changed: 114 additions & 1084 deletions

sample/configs/prebid-config.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ adapters:
1010
enabled: true
1111
rubicon:
1212
enabled: true
13-
startio:
14-
enabled: true
1513
metrics:
1614
prefix: prebid
1715
cache:

src/main/java/org/prebid/server/bidder/startio/StartioBidder.java

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.iab.openrtb.response.BidResponse;
1111
import com.iab.openrtb.response.SeatBid;
1212
import org.apache.commons.collections4.CollectionUtils;
13+
import org.apache.commons.lang3.StringUtils;
1314
import org.prebid.server.bidder.Bidder;
1415
import org.prebid.server.bidder.model.BidderBid;
1516
import org.prebid.server.bidder.model.BidderCall;
@@ -30,21 +31,20 @@
3031

3132
public class StartioBidder implements Bidder<BidRequest> {
3233

33-
private final String endpointUrl;
34-
private final JacksonMapper mapper;
35-
3634
private static final JsonPointer BID_TYPE_POINTER = JsonPointer.valueOf("/prebid/type");
3735
private static final String BID_CURRENCY = "USD";
3836

37+
private final String endpointUrl;
38+
private final JacksonMapper mapper;
39+
3940
public StartioBidder(String endpointUrl, JacksonMapper mapper) {
4041
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
4142
this.mapper = Objects.requireNonNull(mapper);
4243
}
4344

4445
@Override
4546
public final Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
46-
47-
if (hasAppOrSiteId(bidRequest)) {
47+
if (hasNoAppOrSiteId(bidRequest)) {
4848
return Result.withError(BidderError.badInput(
4949
"Bidder requires either app.id or site.id to be specified."));
5050
}
@@ -57,23 +57,37 @@ public final Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest b
5757
final List<BidderError> errors = new ArrayList<>();
5858
final List<Imp> imps = bidRequest.getImp();
5959
for (int i = 0; i < imps.size(); i++) {
60-
Imp imp = imps.get(i);
60+
final Imp imp = imps.get(i);
6161
if (imp.getBanner() == null && imp.getVideo() == null && imp.getXNative() == null) {
6262
errors.add(BidderError.badInput(
6363
"imp[%d]: Unsupported media type, bidder does not support audio.".formatted(i)));
64-
} else {
65-
if (imp.getAudio() != null) {
66-
imp = imp.toBuilder().audio(null).build();
67-
}
68-
requests.add(BidderUtil.defaultRequest(
69-
bidRequest.toBuilder().imp(Collections.singletonList(imp)).build(),
70-
endpointUrl, mapper));
64+
65+
continue;
7166
}
67+
68+
final Imp modifiedImp = imp.getAudio() != null
69+
? imp.toBuilder().audio(null).build()
70+
: imp;
71+
requests.add(BidderUtil.defaultRequest(
72+
bidRequest.toBuilder().imp(Collections.singletonList(modifiedImp)).build(),
73+
endpointUrl, mapper));
74+
7275
}
7376

7477
return Result.of(requests, errors);
7578
}
7679

80+
private static boolean hasNoAppOrSiteId(BidRequest bidRequest) {
81+
final App app = bidRequest.getApp();
82+
final Site site = bidRequest.getSite();
83+
return (app == null || StringUtils.isEmpty(app.getId()))
84+
&& (site == null || StringUtils.isEmpty(site.getId()));
85+
}
86+
87+
private static boolean isSupportedCurrency(List<String> currencies) {
88+
return CollectionUtils.isNotEmpty(currencies) && !currencies.contains(BID_CURRENCY);
89+
}
90+
7791
@Override
7892
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
7993
final BidResponse response;
@@ -101,6 +115,7 @@ private static List<BidderBid> bidsFromResponse(BidResponse bidResponse, List<Bi
101115
.map(SeatBid::getBid)
102116
.filter(Objects::nonNull)
103117
.flatMap(Collection::stream)
118+
.filter(Objects::nonNull)
104119
.map(bid -> constructBidderBid(bid, bidResponse.getCur(), errors))
105120
.filter(Objects::nonNull)
106121
.toList();
@@ -119,11 +134,7 @@ private static BidderBid constructBidderBid(Bid bid, String currency, List<Bidde
119134

120135
private static BidType getBidType(Bid bid) {
121136
final JsonNode ext = bid.getExt();
122-
if (ext == null) {
123-
return null;
124-
}
125-
126-
final JsonNode bidType = ext.at(BID_TYPE_POINTER);
137+
final JsonNode bidType = ext != null ? ext.at(BID_TYPE_POINTER) : null;
127138
if (bidType == null || !bidType.isTextual()) {
128139
return null;
129140
}
@@ -136,15 +147,4 @@ private static BidType getBidType(Bid bid) {
136147
};
137148
}
138149

139-
private static boolean isSupportedCurrency(List<String> currencies) {
140-
return currencies != null && !currencies.isEmpty() && !currencies.contains(BID_CURRENCY);
141-
}
142-
143-
private static boolean hasAppOrSiteId(BidRequest bidRequest) {
144-
final App app = bidRequest.getApp();
145-
final Site site = bidRequest.getSite();
146-
return (app == null || app.getId() == null || app.getId().isEmpty())
147-
&& (site == null || site.getId() == null || site.getId().isEmpty());
148-
}
149-
150150
}

src/test/java/org/prebid/server/bidder/startio/StartioBidderTest.java

Lines changed: 85 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,74 @@ public void makeHttpRequestsShouldReturnErrorForUnsupportedCurrency() {
133133
BidderError.badInput("Unsupported currency, bidder only accepts USD."));
134134
}
135135

136+
@Test
137+
public void makeHttpRequestsShouldReturnErrorWhenImpressionContainsOnlyAudio() {
138+
// given
139+
final Audio audio = Audio.builder().mimes(List.of("audio/mp3")).build();
140+
final BidRequest bidRequest = givenBidRequest(identity(),
141+
impBuilder -> impBuilder.id("Imp01").banner(null).video(null).xNative(null).audio(audio));
142+
143+
// when
144+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
145+
146+
// then
147+
assertThat(result.getValue()).isEmpty();
148+
assertThat(result.getErrors()).hasSize(1).contains(
149+
BidderError.badInput("imp[0]: Unsupported media type, bidder does not support audio."));
150+
}
151+
152+
@Test
153+
public void makeHttpRequestsShouldRemoveAudioFromImpressionIfItContainsMultipleMediaTypes() {
154+
155+
// given
156+
final Audio audio = Audio.builder().mimes(List.of("audio/mp3")).build();
157+
final Banner banner = Banner.builder().build();
158+
final BidRequest bidRequest = givenBidRequest(identity(),
159+
impBuilder -> impBuilder.id("Imp01").audio(audio).banner(banner));
160+
161+
// when
162+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
163+
164+
// then
165+
assertThat(result.getErrors()).isEmpty();
166+
assertThat(result.getValue()).hasSize(1)
167+
.extracting(HttpRequest::getPayload)
168+
.flatExtracting(BidRequest::getImp)
169+
.allSatisfy(impression -> {
170+
assertThat(impression.getAudio()).isEqualTo(null);
171+
assertThat(impression.getBanner()).isEqualTo(banner);
172+
});
173+
}
174+
175+
@Test
176+
public void makeHttpRequestsShouldSplitRequestByImpressions() {
177+
// given
178+
final Banner banner = Banner.builder().w(100).h(100).build();
179+
final Video video = Video.builder().w(100).h(200).build();
180+
final Native nativeImp = Native.builder().request("request").build();
181+
final Imp imp1 = givenImp(impBuilder -> impBuilder.id("imp1").banner(banner));
182+
final Imp imp2 = givenImp(impBuilder -> impBuilder.id("imp2").video(video));
183+
final Imp imp3 = givenImp(impBuilder -> impBuilder.id("imp3").xNative(nativeImp));
184+
final BidRequest bidRequest = givenBidRequest(
185+
requestCustomizer -> requestCustomizer.imp(List.of(imp1, imp2, imp3)),
186+
identity());
187+
188+
// when
189+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
190+
191+
// then
192+
assertThat(result.getErrors()).isEmpty();
193+
assertThat(result.getValue()).hasSize(3)
194+
.extracting(HttpRequest::getPayload)
195+
.extracting(BidRequest::getImp)
196+
.allSatisfy(imps -> assertThat(imps).hasSize(1))
197+
.extracting(List::getFirst)
198+
.satisfiesExactlyInAnyOrder(
199+
impression -> assertThat(impression).isEqualTo(imp1),
200+
impression -> assertThat(impression).isEqualTo(imp2),
201+
impression -> assertThat(impression).isEqualTo(imp3));
202+
}
203+
136204
@Test
137205
public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
138206
// given
@@ -153,11 +221,10 @@ public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
153221
@Test
154222
public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException {
155223
// given
156-
final BidRequest bidRequest = givenBidRequest(identity());
157-
final BidderCall<BidRequest> httpCall = givenHttpCall(bidRequest, mapper.writeValueAsString(null));
224+
final BidderCall<BidRequest> httpCall = givenHttpCall(null, mapper.writeValueAsString(null));
158225

159226
// when
160-
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);
227+
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
161228

162229
// then
163230
assertThat(result.getErrors()).isEmpty();
@@ -167,12 +234,11 @@ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProces
167234
@Test
168235
public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException {
169236
// given
170-
final BidRequest bidRequest = givenBidRequest(identity());
171-
final BidderCall<BidRequest> httpCall = givenHttpCall(bidRequest,
237+
final BidderCall<BidRequest> httpCall = givenHttpCall(null,
172238
mapper.writeValueAsString(BidResponse.builder().build()));
173239

174240
// when
175-
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);
241+
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
176242

177243
// then
178244
assertThat(result.getErrors()).isEmpty();
@@ -182,8 +248,7 @@ public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws Jso
182248
@Test
183249
public void makeBidsShouldReportErrorAndSkipBidIfCannotParseBidType() throws JsonProcessingException {
184250
// given
185-
final BidRequest bidRequest = givenBidRequest(identity());
186-
final BidderCall<BidRequest> httpCall = givenHttpCall(bidRequest,
251+
final BidderCall<BidRequest> httpCall = givenHttpCall(null,
187252
givenBidResponse(
188253
givenBid("001", BidType.banner),
189254
givenBid("002", BidType.video),
@@ -194,11 +259,10 @@ public void makeBidsShouldReportErrorAndSkipBidIfCannotParseBidType() throws Jso
194259
mapper.createObjectNode().put("prebid", false)).build(),
195260
givenBid("007", BidType.banner).toBuilder().ext(
196261
mapper.createObjectNode().set("prebid",
197-
mapper.createObjectNode().put("type", "not a banner"))).build()
198-
));
262+
mapper.createObjectNode().put("type", "not a banner"))).build()));
199263

200264
// when
201-
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);
265+
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
202266

203267
// then
204268
assertThat(result.getErrors()).hasSize(4).allSatisfy(
@@ -208,97 +272,20 @@ public void makeBidsShouldReportErrorAndSkipBidIfCannotParseBidType() throws Jso
208272
bid -> assertThat(bid.getBid().getImpid()).isIn(List.of("001", "002", "003")));
209273
}
210274

211-
@Test
212-
public void makeHttpRequestsShouldReturnErrorWhenImpressionContainsOnlyAudio() {
213-
214-
// given
215-
final Audio audio = Audio.builder().mimes(List.of("audio/mp3")).build();
216-
final BidRequest bidRequest = givenBidRequest(identity(),
217-
impBuilder -> impBuilder.id("Imp01").banner(null).video(null).xNative(null).audio(audio));
218-
219-
// when
220-
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
221-
222-
// then
223-
assertThat(result.getValue()).isEmpty();
224-
assertThat(result.getErrors()).hasSize(1).contains(
225-
BidderError.badInput("imp[0]: Unsupported media type, bidder does not support audio."));
226-
}
227-
228-
@Test
229-
public void makeHttpRequestsShouldRemoveAudioFromImpressionIfItContainsMultipleMediaTypes() {
230-
231-
// given
232-
final Audio audio = Audio.builder().mimes(List.of("audio/mp3")).build();
233-
final Banner banner = Banner.builder().build();
234-
final BidRequest bidRequest = givenBidRequest(identity(),
235-
impBuilder -> impBuilder.id("Imp01").audio(audio).banner(banner));
236-
237-
// when
238-
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
239-
240-
// then
241-
assertThat(result.getErrors()).isEmpty();
242-
assertThat(result.getValue()).hasSize(1)
243-
.extracting(HttpRequest::getPayload).extracting(BidRequest::getImp).extracting(List::getFirst)
244-
.allSatisfy(impression -> {
245-
assertThat(impression.getAudio()).isEqualTo(null);
246-
assertThat(impression.getBanner()).isEqualTo(banner);
247-
});
248-
}
249-
250-
@Test
251-
public void makeHttpRequestsShouldSplitRequestByImpressions() {
252-
// given
253-
final Banner banner = Banner.builder().w(100).h(100).build();
254-
final Video video = Video.builder().w(100).h(200).build();
255-
final Native nativeImp = Native.builder().request("request").build();
256-
final Imp imp1 = givenImp(impBuilder -> impBuilder.id("imp1").banner(banner));
257-
final Imp imp2 = givenImp(impBuilder -> impBuilder.id("imp2").video(video));
258-
final Imp imp3 = givenImp(impBuilder -> impBuilder.id("imp3").xNative(nativeImp));
259-
final BidRequest bidRequest = givenBidRequest(
260-
requestCustomizer -> requestCustomizer.imp(List.of(imp1, imp2, imp3)),
261-
identity());
262-
263-
// when
264-
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
265-
266-
// then
267-
assertThat(result.getErrors()).isEmpty();
268-
final List<HttpRequest<BidRequest>> httpRequests = result.getValue();
269-
assertThat(httpRequests).hasSize(3);
270-
271-
// Each resulting request contains single impression
272-
assertThat(httpRequests)
273-
.extracting(HttpRequest::getPayload)
274-
.extracting(BidRequest::getImp)
275-
.allSatisfy(impressions -> assertThat(impressions).hasSize(1));
276-
277-
// All impressions are correctly passed in the resulting requests
278-
assertThat(httpRequests)
279-
.extracting(HttpRequest::getPayload)
280-
.extracting(BidRequest::getImp)
281-
.extracting(List::getFirst)
282-
.satisfiesExactlyInAnyOrder(
283-
impression -> assertThat(impression).isEqualTo(imp1),
284-
impression -> assertThat(impression).isEqualTo(imp2),
285-
impression -> assertThat(impression).isEqualTo(imp3)
286-
);
275+
private static BidRequest givenBidRequest(UnaryOperator<Imp.ImpBuilder> impCustomizer) {
276+
return givenBidRequest(identity(), impCustomizer);
287277
}
288278

289279
private static BidRequest givenBidRequest(
290280
UnaryOperator<BidRequest.BidRequestBuilder> bidRequestCustomizer,
291281
UnaryOperator<Imp.ImpBuilder> impCustomizer) {
292282

293-
return bidRequestCustomizer.apply(BidRequest.builder().app(App.builder().id("appId").build())
283+
return bidRequestCustomizer.apply(BidRequest.builder()
284+
.app(App.builder().id("appId").build())
294285
.imp(singletonList(givenImp(impCustomizer))))
295286
.build();
296287
}
297288

298-
private static BidRequest givenBidRequest(UnaryOperator<Imp.ImpBuilder> impCustomizer) {
299-
return givenBidRequest(identity(), impCustomizer);
300-
}
301-
302289
private static Imp givenImp(UnaryOperator<Imp.ImpBuilder> impCustomizer) {
303290
return impCustomizer.apply(Imp.builder().id("345").banner(Banner.builder().build())).build();
304291
}
@@ -321,8 +308,12 @@ private static BidResponse givenBidResponse(Bid... bids) {
321308
}
322309

323310
private static Bid givenBid(String impid, BidType bidType) {
324-
return Bid.builder().impid(impid).ext(mapper.createObjectNode().set("prebid",
325-
mapper.createObjectNode().put("type", bidType.getName()))).build();
311+
return Bid.builder()
312+
.impid(impid)
313+
.ext(mapper.createObjectNode()
314+
.set("prebid", mapper.createObjectNode()
315+
.put("type", bidType.getName())))
316+
.build();
326317
}
327318

328319
}

0 commit comments

Comments
 (0)