Skip to content

Commit

Permalink
Add Vrtcal bidder implementation (prebid#390)
Browse files Browse the repository at this point in the history
  • Loading branch information
RossGoncharuk authored and rpanchyk committed Jul 4, 2019
1 parent 2c16de6 commit 69c9577
Show file tree
Hide file tree
Showing 14 changed files with 729 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/main/java/org/prebid/server/bidder/vrtcal/VrtcalBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.prebid.server.bidder.vrtcal;

import com.iab.openrtb.request.Imp;
import org.prebid.server.bidder.OpenrtbBidder;
import org.prebid.server.proto.openrtb.ext.request.vrtcal.ExtImpVrtcal;
import org.prebid.server.proto.openrtb.ext.response.BidType;

import java.util.List;

public class VrtcalBidder extends OpenrtbBidder<ExtImpVrtcal> {
public VrtcalBidder(String endpointUrl) {
super(endpointUrl, RequestCreationStrategy.SINGLE_REQUEST, ExtImpVrtcal.class);
}

@Override
protected BidType getBidType(String impId, List<Imp> imps) {
return BidType.banner;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.prebid.server.proto.openrtb.ext.request.vrtcal;

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

/**
* Defines the contract for bidRequest.imp[i].ext.Vrtcal
*/
@AllArgsConstructor(staticName = "of")
@Value
public class ExtImpVrtcal {

@JsonProperty("Just_an_unused_vrtcal_param")
String justAnUnusedVrtcalParam;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.prebid.server.spring.config.bidder;

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.vrtcal.VrtcalBidder;
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
import org.prebid.server.spring.config.bidder.util.BidderInfoCreator;
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
import org.prebid.server.spring.env.YamlPropertySourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.validation.constraints.NotBlank;

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

private static final String BIDDER_NAME = "vrtcal";

@Value("${external-url}")
@NotBlank
private String externalUrl;

@Autowired
@Qualifier("vrtcalConfigurationProperties")
private BidderConfigurationProperties configProperties;

@Bean("vrtcalConfigurationProperties")
@ConfigurationProperties("adapters.vrtcal")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

@Bean
BidderDeps vrtcalBidderDeps() {
return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(configProperties)
.bidderInfo(BidderInfoCreator.create(configProperties))
.usersyncerCreator(UsersyncerCreator.create(configProperties.getUsersync(), externalUrl))
.bidderCreator(() -> new VrtcalBidder(configProperties.getEndpoint()))
.assemble();
}
}
20 changes: 20 additions & 0 deletions src/main/resources/bidder-config/vrtcal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
adapters:
vrtcal:
enabled: false
endpoint: http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804
pbs-enforces-gdpr: true
deprecated-names:
aliases:
meta-info:
maintainer-email: [email protected]
app-media-types:
- banner
site-media-types:
supported-vendors:
vendor-id: 0
usersync:
url:
redirect-url:
cookie-family-name: vrtcal
type: redirect
support-cors: false
13 changes: 13 additions & 0 deletions src/main/resources/static/bidder-params/vrtcal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Vrtcal Adapter Params",
"description": "A schema which validates params accepted by the Vrtcal adapter",
"type": "object",
"properties": {
"just_an_unused_vrtcal_param": {
"type": "string",
"description": "We only have this param as the prebid server crashes without at least 1 custom param; Not set to required to achieve parameter-less custom functionality"
}
},
"required": []
}
163 changes: 163 additions & 0 deletions src/test/java/org/prebid/server/bidder/vrtcal/VrtcalBidderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package org.prebid.server.bidder.vrtcal;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import org.junit.Before;
import org.junit.Test;
import org.prebid.server.VertxTest;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpCall;
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.vrtcal.ExtImpVrtcal;

import java.util.List;
import java.util.function.Function;

import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.function.Function.identity;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.prebid.server.proto.openrtb.ext.response.BidType.banner;

public class VrtcalBidderTest extends VertxTest {

private static final String ENDPOINT_URL = "https://test.endpoint.com";

private VrtcalBidder vrtcalBidder;

@Before
public void setUp() {
vrtcalBidder = new VrtcalBidder(ENDPOINT_URL);
}

@Test
public void creationShouldFailOnInvalidEndpointUrl() {
assertThatIllegalArgumentException().isThrownBy(() -> new VrtcalBidder("invalid_url"));
}

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

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

// then
assertThat(result.getErrors()).hasSize(1);
assertThat(result.getErrors().get(0).getMessage()).startsWith("Cannot deserialize instance");
assertThat(result.getValue()).isEmpty();
}

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

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

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).hasSize(1)
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
.containsOnly(bidRequest);
}

@Test
public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
// given
final HttpCall<BidRequest> httpCall = givenHttpCall(null, "invalid");

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

// then
assertThat(result.getErrors()).hasSize(1);
assertThat(result.getErrors().get(0).getMessage()).startsWith("Failed to decode: Unrecognized token");
assertThat(result.getErrors().get(0).getType()).isEqualTo(BidderError.Type.bad_server_response);
assertThat(result.getValue()).isEmpty();
}

@Test
public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException {
// given
final HttpCall<BidRequest> httpCall = givenHttpCall(null,
mapper.writeValueAsString(null));

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

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

@Test
public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException {
// given
final HttpCall<BidRequest> httpCall = givenHttpCall(null,
mapper.writeValueAsString(BidResponse.builder().build()));

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

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

@Test
public void makeBidsShouldAlwaysReturnBannerBid() throws JsonProcessingException {
// given
final HttpCall<BidRequest> httpCall = givenHttpCall(
BidRequest.builder().imp(singletonList(Imp.builder().build())).build(),
mapper.writeValueAsString(givenBidResponse(identity())));

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

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.containsOnly(BidderBid.of(Bid.builder().build(), banner, "USD"));
}

@Test
public void extractTargetingShouldReturnEmptyMap() {
assertThat(vrtcalBidder.extractTargeting(mapper.createObjectNode())).isEqualTo(emptyMap());
}

private static BidResponse givenBidResponse(Function<Bid.BidBuilder, Bid.BidBuilder> bidCustomizer) {
return BidResponse.builder()
.seatbid(singletonList(SeatBid.builder()
.bid(singletonList(bidCustomizer.apply(Bid.builder()).build()))
.build()))
.build();
}

private static HttpCall<BidRequest> givenHttpCall(BidRequest bidRequest, String body) {
return HttpCall.success(
HttpRequest.<BidRequest>builder().payload(bidRequest).build(),
HttpResponse.of(200, null, body),
null);
}
}
54 changes: 54 additions & 0 deletions src/test/java/org/prebid/server/it/VrtcalTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.prebid.server.it;

import io.restassured.response.Response;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static io.restassured.RestAssured.given;
import static java.util.Collections.singletonList;

@RunWith(SpringRunner.class)
public class VrtcalTest extends IntegrationTest {

@Test
public void openrtb2AuctionShouldRespondWithBidsFromVrtcal() throws IOException, JSONException {
// given
// Vrtcal bid response for imp 001
wireMockRule.stubFor(post(urlPathEqualTo("/vrtcal-exchange"))
.withRequestBody(equalToJson(jsonFrom("openrtb2/vrtcal/test-vrtcal-bid-request-1.json")))
.willReturn(aResponse().withBody(jsonFrom("openrtb2/vrtcal/test-vrtcal-bid-response-1.json"))));

// pre-bid cache
wireMockRule.stubFor(post(urlPathEqualTo("/cache"))
.withRequestBody(equalToJson(jsonFrom("openrtb2/vrtcal/test-cache-vrtcal-request.json")))
.willReturn(aResponse().withBody(jsonFrom("openrtb2/vrtcal/test-cache-vrtcal-response.json"))));

// when
final Response response = given(spec)
.header("Referer", "http://www.example.com")
.header("X-Forwarded-For", "192.168.244.1")
.header("User-Agent", "userAgent")
.header("Origin", "http://www.example.com")
// this uids cookie value stands for {"uids":{"vrtcal":"VR-UID"}}
.cookie("uids", "eyJ1aWRzIjp7InZydGNhbCI6IlZSLVVJRCJ9fQ==")
.body(jsonFrom("openrtb2/vrtcal/test-auction-vrtcal-request.json"))
.post("/openrtb2/auction");

// then
final String expectedAuctionResponse = openrtbAuctionResponseFrom(
"openrtb2/vrtcal/test-auction-vrtcal-response.json",
response, singletonList("vrtcal"));

JSONAssert.assertEquals(expectedAuctionResponse, response.asString(), JSONCompareMode.NON_EXTENSIBLE);
}
}
Loading

0 comments on commit 69c9577

Please sign in to comment.