Skip to content
Merged
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
@@ -1,21 +1,25 @@
package org.prebid.server.bidder.smilewanted;

import com.fasterxml.jackson.core.type.TypeReference;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import org.apache.commons.collections4.CollectionUtils;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderCall;
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.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.smilewanted.ExtImpSmilewanted;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.BidderUtil;
import org.prebid.server.util.HttpUtil;

import java.util.Collections;
Expand All @@ -27,6 +31,11 @@ public class SmileWantedBidder implements Bidder<BidRequest> {
private static final String SW_INTEGRATION_TYPE = "prebid_server";
private static final String X_OPENRTB_VERSION = "2.5";
private static final int DEFAULT_AT = 1;
private static final String ZONE_ID_MACRO = "{{ZoneId}}";

private static final TypeReference<ExtPrebid<?, ExtImpSmilewanted>> SMILEWANTED_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};

private final String endpointUrl;
private final JacksonMapper mapper;
Expand All @@ -38,15 +47,30 @@ public SmileWantedBidder(String endpointUrl, JacksonMapper mapper) {

@Override
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
final ExtImpSmilewanted extImpSmilewanted;

try {
extImpSmilewanted = parseImpExt(request.getImp().getFirst());
} catch (PreBidException e) {
return Result.withError(BidderError.badInput(e.getMessage()));
}

final BidRequest outgoingRequest = request.toBuilder().at(DEFAULT_AT).build();
final String url = endpointUrl.replace(ZONE_ID_MACRO, HttpUtil.encodeUrl(extImpSmilewanted.getZoneId()));

return Result.withValue(HttpRequest.<BidRequest>builder()
.method(HttpMethod.POST)
.uri(endpointUrl)
.headers(createHeaders())
.payload(outgoingRequest)
.body(mapper.encodeToBytes(outgoingRequest))
.build());
return Result.withValue(BidderUtil.defaultRequest(
outgoingRequest,
createHeaders(),
url,
mapper));
}

private ExtImpSmilewanted parseImpExt(Imp imp) {
try {
return mapper.mapper().convertValue(imp.getExt(), SMILEWANTED_EXT_TYPE_REFERENCE).getBidder();
} catch (IllegalArgumentException e) {
throw new PreBidException("Missing bidder ext in impression with id: " + imp.getId());
}
}

private static MultiMap createHeaders() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.prebid.server.proto.openrtb.ext.request.smilewanted;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;

@Value(staticConstructor = "of")
public class ExtImpSmilewanted {

@JsonProperty("zoneId")
String zoneId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

@Configuration
@PropertySource(value = "classpath:/bidder-config/smilewanted.yaml", factory = YamlPropertySourceFactory.class)
public class SimpleWantedConfiguration {
public class SmileWantedConfiguration {

private static final String BIDDER_NAME = "smilewanted";

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/bidder-config/smilewanted.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
adapters:
smilewanted:
endpoint: https://prebid-server.smilewanted.com
endpoint: https://prebid-server.smilewanted.com/java/{{ZoneId}}
meta-info:
maintainer-email: tech@smilewanted.com
app-media-types:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@
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.smilewanted.ExtImpSmilewanted;
import org.prebid.server.util.HttpUtil;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
Expand All @@ -32,7 +36,7 @@

public class SmileWantedBidderTest extends VertxTest {

private static final String ENDPOINT_URL = "https://{{Host}}/test?param={{PublisherId}}";
private static final String ENDPOINT_URL = "https://prebid-server.smilewanted.com/java/{{ZoneId}}";

private final SmileWantedBidder target = new SmileWantedBidder(ENDPOINT_URL, jacksonMapper);

Expand All @@ -41,10 +45,76 @@ public void creationShouldFailOnInvalidEndpointUrl() {
assertThatIllegalArgumentException().isThrownBy(() -> new SmileWantedBidder("invalid_url", jacksonMapper));
}

@Test
public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() {
// given
final BidRequest bidRequest = BidRequest.builder()
.imp(singletonList(Imp.builder()
.id("123")
.ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))
.build()))
.build();

// 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()).startsWith("Missing bidder ext in impression with id: 123");
});
}

@Test
public void makeHttpRequestsShouldReturnSingleRequest() {
// given
final BidRequest bidRequest = BidRequest.builder()
.imp(List.of(
givenImp("zone123"),
Imp.builder()
.id("456")
.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmilewanted.of("zone123"))))
.build()))
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).hasSize(1);
final HttpRequest<BidRequest> httpRequest = result.getValue().get(0);
assertThat(httpRequest.getPayload()).isNotNull();
assertThat(httpRequest.getPayload().getImp()).hasSize(2);
assertThat(httpRequest.getPayload().getAt()).isEqualTo(1);
assertThat(httpRequest.getImpIds()).containsExactlyInAnyOrder("123", "456");
}

@Test
public void makeHttpRequestsShouldBuildCorrectEndpointUrlWithZoneId() {
// given
final BidRequest bidRequest = BidRequest.builder()
.imp(singletonList(givenImp("zone456")))
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.extracting(HttpRequest::getUri)
.containsExactly("https://prebid-server.smilewanted.com/java/zone456");
}

@Test
public void makeHttpRequestsShouldCorrectlyAddHeaders() {
// given
final BidRequest bidRequest = BidRequest.builder().build();
final BidRequest bidRequest = BidRequest.builder()
.imp(singletonList(givenImp("zone123")))
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
Expand All @@ -65,7 +135,9 @@ public void makeHttpRequestsShouldCorrectlyAddHeaders() {
@Test
public void makeHttpRequestsShouldSetAtToOne() {
// given
final BidRequest bidRequest = BidRequest.builder().build();
final BidRequest bidRequest = BidRequest.builder()
.imp(singletonList(givenImp("zone123")))
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
Expand Down Expand Up @@ -98,7 +170,7 @@ public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
@Test
public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(null, mapper.writeValueAsString(null));
final BidderCall<BidRequest> httpCall = givenHttpCall(null, (BidResponse) null);

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
Expand All @@ -111,8 +183,7 @@ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProces
@Test
public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(null,
mapper.writeValueAsString(BidResponse.builder().build()));
final BidderCall<BidRequest> httpCall = givenHttpCall(null, BidResponse.builder().build());

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
Expand All @@ -130,8 +201,7 @@ public void makeBidsShouldReturnVideoBidIfVideoIsPresentInRequestImpAndCorrespon
BidRequest.builder()
.imp(singletonList(Imp.builder().id("123").video(Video.builder().build()).build()))
.build(),
mapper.writeValueAsString(
givenBidResponse(bidBuilder -> bidBuilder.impid("123"))));
givenBidResponse(bidBuilder -> bidBuilder.impid("123")));

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
Expand All @@ -148,8 +218,7 @@ public void makeBidsShouldReturnBannerBidIfVideoIsAbsentInRequestImp() throws Js
final BidderCall<BidRequest> httpCall = givenHttpCall(BidRequest.builder()
.imp(singletonList(Imp.builder().id("123").build()))
.build(),
mapper.writeValueAsString(
givenBidResponse(bidBuilder -> bidBuilder.impid("123"))));
givenBidResponse(bidBuilder -> bidBuilder.impid("123")));

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
Expand All @@ -160,6 +229,102 @@ public void makeBidsShouldReturnBannerBidIfVideoIsAbsentInRequestImp() throws Js
.containsOnly(BidderBid.of(Bid.builder().impid("123").build(), banner, null));
}

@Test
public void makeBidsShouldReturnMultipleBidsFromSingleSeatBid() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(
BidRequest.builder()
.imp(List.of(
Imp.builder().id("123").build(),
Imp.builder().id("456").video(Video.builder().build()).build()))
.build(),
BidResponse.builder()
.cur("USD")
.seatbid(singletonList(SeatBid.builder()
.bid(List.of(
Bid.builder().impid("123").price(BigDecimal.valueOf(1.0)).build(),
Bid.builder().impid("456").price(BigDecimal.valueOf(2.0)).build()))
.build()))
.build());

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).hasSize(2)
.containsExactlyInAnyOrder(
BidderBid.of(Bid.builder().impid("123").price(BigDecimal.valueOf(1.0)).build(), banner, "USD"),
BidderBid.of(Bid.builder().impid("456").price(BigDecimal.valueOf(2.0)).build(), video, "USD"));
}

@Test
public void makeBidsShouldFilterNullBids() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(
BidRequest.builder()
.imp(singletonList(Imp.builder().id("123").build()))
.build(),
BidResponse.builder()
.seatbid(singletonList(SeatBid.builder()
.bid(Arrays.asList(
Bid.builder().impid("123").build(),
null,
Bid.builder().impid("456").build()))
.build()))
.build());

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).hasSize(2)
.extracting(BidderBid::getBid)
.extracting(Bid::getImpid)
.containsExactlyInAnyOrder("123", "456");
}

@Test
public void makeBidsShouldReturnBidWithCurrency() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(
BidRequest.builder()
.imp(singletonList(Imp.builder().id("123").build()))
.build(),
BidResponse.builder()
.cur("EUR")
.seatbid(singletonList(SeatBid.builder()
.bid(singletonList(Bid.builder().impid("123").build()))
.build()))
.build());

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.extracting(BidderBid::getBidCurrency)
.containsExactly("EUR");
}

@Test
public void makeBidsShouldReturnEmptyListIfSeatBidIsEmpty() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(null,
BidResponse.builder()
.seatbid(singletonList(SeatBid.builder().build()))
.build());

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).isEmpty();
}

private static BidResponse givenBidResponse(Function<Bid.BidBuilder, Bid.BidBuilder> bidCustomizer) {
return BidResponse.builder()
.seatbid(singletonList(SeatBid.builder().bid(singletonList(bidCustomizer.apply(Bid.builder()).build()))
Expand All @@ -173,4 +338,16 @@ private static BidderCall<BidRequest> givenHttpCall(BidRequest bidRequest, Strin
HttpResponse.of(200, null, body),
null);
}

private static BidderCall<BidRequest> givenHttpCall(BidRequest bidRequest, BidResponse bidResponse)
throws JsonProcessingException {
return givenHttpCall(bidRequest, mapper.writeValueAsString(bidResponse));
}

private static Imp givenImp(String zoneId) {
return Imp.builder()
.id("123")
.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmilewanted.of(zoneId))))
.build();
}
}
2 changes: 1 addition & 1 deletion src/test/java/org/prebid/server/it/SmileWantedTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class SmileWantedTest extends IntegrationTest {
@Test
public void openrtb2AuctionShouldRespondWithBidsFromSmileWanted() throws IOException, JSONException {
// given
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smilewanted-exchange"))
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smilewanted-exchange/java/someZoneId"))
.withRequestBody(equalToJson(jsonFrom("openrtb2/smilewanted/test-smilewanted-bid-request.json")))
.willReturn(aResponse().withBody(jsonFrom(
"openrtb2/smilewanted/test-smilewanted-bid-response.json"))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ adapters.smarthub.aliases.artechnology.endpoint=http://localhost:8090/artechnolo
adapters.smartyads.enabled=true
adapters.smartyads.endpoint=http://localhost:8090/smartyads-exchange
adapters.smilewanted.enabled=true
adapters.smilewanted.endpoint=http://localhost:8090/smilewanted-exchange
adapters.smilewanted.endpoint=http://localhost:8090/smilewanted-exchange/java/{{ZoneId}}
adapters.smoot.enabled=true
adapters.smoot.endpoint=http://localhost:8090/smoot-exchange
adapters.smrtconnect.enabled=true
Expand Down
Loading