diff --git a/.github/stale.yml b/.github/stale.yml
index 1246f73b343..c59ef23f554 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -1,8 +1,10 @@
-# Number of days of inactivity before an issue becomes stale
-daysUntilStale: 7
-# Number of days of inactivity before a stale issue is closed
-daysUntilClose: 7
-# Issues with these labels will never be considered stale
+pulls:
+ daysUntilStale: 7
+ daysUntilClose: 30
+issues:
+ daysUntilStale: 30
+ daysUntilClose: 60
+# Items with these labels will never be considered stale
exemptLabels:
- pinned
- security
diff --git a/.gitignore b/.gitignore
index c2cbc1e97d5..60c24e79c0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,10 @@ debug
pbs.*
inventory_url.yaml
+# generated log files during tests
+analytics/config/testFiles/
+analytics/filesystem/testFiles/
+
# autogenerated version file
# static/version.txt
diff --git a/.travis.yml b/.travis.yml
index b46dd356e73..60ee49faf68 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,8 @@
language: go
go:
- - '1.12'
- '1.13'
+ - '1.14.2'
go_import_path: github.com/PubMatic-OpenWrap/prebid-server
diff --git a/Dockerfile b/Dockerfile
index a8fea9c33f6..2c60b9e39b0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,8 +3,8 @@ RUN apt-get update && \
apt-get -y upgrade && \
apt-get install -y wget
RUN cd /tmp && \
- wget https://dl.google.com/go/go1.12.7.linux-amd64.tar.gz && \
- tar -xf go1.12.7.linux-amd64.tar.gz && \
+ wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz && \
+ tar -xf go1.14.2.linux-amd64.tar.gz && \
mv go /usr/local
RUN mkdir -p /app/prebid-server/
WORKDIR /app/prebid-server/
diff --git a/README.md b/README.md
index f0e0b47572e..b3c795bf803 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[![Build Status](https://travis-ci.org/PubMatic-OpenWrap/prebid-server.svg?branch=master)](https://travis-ci.org/PubMatic-OpenWrap/prebid-server)
+[![Build Status](https://travis-ci.org/prebid/prebid-server.svg?branch=master)](https://travis-ci.org/prebid/prebid-server)
[![Go Report Card](https://goreportcard.com/badge/github.com/PubMatic-OpenWrap/prebid-server?style=flat-square)](https://goreportcard.com/report/github.com/PubMatic-OpenWrap/prebid-server)
# Prebid Server
@@ -18,9 +18,10 @@ For more information, see:
## Installation
-First install [Go 1.12](https://golang.org/doc/install) latest version.
+First install [Go](https://golang.org/doc/install) version 1.13 or newer.
+
Note that prebid-server is using [Go modules](https://blog.golang.org/using-go-modules).
-If using Go version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`.
+We officially support the most recent two major versions of the Go runtime. However, if you'd like to use a version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`.
Download and prepare Prebid Server:
diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go
index 30d2f59be94..7bb06d0716f 100644
--- a/adapters/adapterstest/test_json.go
+++ b/adapters/adapterstest/test_json.go
@@ -208,7 +208,7 @@ func diffErrorLists(t *testing.T, description string, actual []error, expected [
t.Helper()
if len(expected) != len(actual) {
- t.Fatalf("%s had wrong error count. Expected %d, got %d", description, len(expected), len(actual))
+ t.Fatalf("%s had wrong error count. Expected %d, got %d (%v)", description, len(expected), len(actual), actual)
}
for i := 0; i < len(actual); i++ {
if expected[i].Comparison == "literal" {
@@ -301,7 +301,7 @@ func diffJson(t *testing.T, description string, actual []byte, expected []byte)
if diff.Modified() {
var left interface{}
if err := json.Unmarshal(actual, &left); err != nil {
- t.Fatalf("%s json did not match, but unmarhsalling failed. %v", description, err)
+ t.Fatalf("%s json did not match, but unmarshalling failed. %v", description, err)
}
printer := formatter.NewAsciiFormatter(left, formatter.AsciiFormatterConfig{
ShowArrayIndex: true,
diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go
new file mode 100644
index 00000000000..069609f4262
--- /dev/null
+++ b/adapters/adgeneration/adgeneration.go
@@ -0,0 +1,260 @@
+package adgeneration
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+type AdgenerationAdapter struct {
+ endpoint string
+ version string
+ defaultCurrency string
+}
+
+// Server Responses
+type adgServerResponse struct {
+ Locationid string `json:"locationid"`
+ Dealid string `json:"dealid"`
+ Ad string `json:"ad"`
+ Beacon string `json:"beacon"`
+ Beaconurl string `json:"beaconurl"`
+ Cpm float64 `jsons:"cpm"`
+ Creativeid string `json:"creativeid"`
+ H uint64 `json:"h"`
+ W uint64 `json:"w"`
+ Ttl uint64 `json:"ttl"`
+ Vastxml string `json:"vastxml,omitempty"`
+ LandingUrl string `json:"landing_url"`
+ Scheduleid string `json:"scheduleid"`
+ Results []interface{} `json:"results"`
+}
+
+func (adg *AdgenerationAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ numRequests := len(request.Imp)
+ var errs []error
+
+ if numRequests == 0 {
+ errs = append(errs, &errortypes.BadInput{
+ Message: "No impression in the bid request",
+ })
+ return nil, errs
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ bidRequestArray := make([]*adapters.RequestData, 0, numRequests)
+
+ for index := 0; index < numRequests; index++ {
+ bidRequestUri, err := adg.getRequestUri(request, index)
+ if err != nil {
+ errs = append(errs, err)
+ return nil, errs
+ }
+ bidRequest := &adapters.RequestData{
+ Method: "GET",
+ Uri: bidRequestUri,
+ Body: nil,
+ Headers: headers,
+ }
+ bidRequestArray = append(bidRequestArray, bidRequest)
+ }
+
+ return bidRequestArray, errs
+}
+
+func (adg *AdgenerationAdapter) getRequestUri(request *openrtb.BidRequest, index int) (string, error) {
+ imp := request.Imp[index]
+ adgExt, err := unmarshalExtImpAdgeneration(&imp)
+ if err != nil {
+ return "", &errortypes.BadInput{
+ Message: err.Error(),
+ }
+ }
+ uriObj, err := url.Parse(adg.endpoint)
+ if err != nil {
+ return "", &errortypes.BadInput{
+ Message: err.Error(),
+ }
+ }
+ v := adg.getRawQuery(adgExt.Id, request, &imp)
+ uriObj.RawQuery = v.Encode()
+ return uriObj.String(), err
+}
+
+func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb.BidRequest, imp *openrtb.Imp) *url.Values {
+ v := url.Values{}
+ v.Set("posall", "SSPLOC")
+ v.Set("id", id)
+ v.Set("sdktype", "0")
+ v.Set("hb", "true")
+ v.Set("t", "json3")
+ v.Set("currency", adg.getCurrency(request))
+ v.Set("sdkname", "prebidserver")
+ v.Set("adapterver", adg.version)
+ adSize := getSizes(imp)
+ if adSize != "" {
+ v.Set("size", adSize)
+ }
+ if request.Site != nil && request.Site.Page != "" {
+ v.Set("tp", request.Site.Page)
+ }
+ return &v
+}
+
+func unmarshalExtImpAdgeneration(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdgeneration, error) {
+ var bidderExt adapters.ExtImpBidder
+ var adgExt openrtb_ext.ExtImpAdgeneration
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, err
+ }
+ if err := json.Unmarshal(bidderExt.Bidder, &adgExt); err != nil {
+ return nil, err
+ }
+ if adgExt.Id == "" {
+ return nil, errors.New("No Location ID in ExtImpAdgeneration.")
+ }
+ return &adgExt, nil
+}
+
+func getSizes(imp *openrtb.Imp) string {
+ if imp.Banner == nil || len(imp.Banner.Format) == 0 {
+ return ""
+ }
+ var sizeStr string
+ for _, v := range imp.Banner.Format {
+ sizeStr += strconv.FormatUint(v.W, 10) + "×" + strconv.FormatUint(v.H, 10) + ","
+ }
+ if len(sizeStr) > 0 && strings.LastIndex(sizeStr, ",") == len(sizeStr)-1 {
+ sizeStr = sizeStr[:len(sizeStr)-1]
+ }
+ return sizeStr
+}
+
+func (adg *AdgenerationAdapter) getCurrency(request *openrtb.BidRequest) string {
+ if len(request.Cur) <= 0 {
+ return adg.defaultCurrency
+ } else {
+ for _, c := range request.Cur {
+ if adg.defaultCurrency == c {
+ return c
+ }
+ }
+ return request.Cur[0]
+ }
+}
+
+func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+ if response.StatusCode == http.StatusBadRequest {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+ }}
+ }
+ if response.StatusCode != http.StatusOK {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+ }}
+ }
+ var bidResp adgServerResponse
+ err := json.Unmarshal(response.Body, &bidResp)
+ if err != nil {
+ return nil, []error{err}
+ }
+ if len(bidResp.Results) <= 0 {
+ return nil, nil
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+ var impId string
+ var bitType openrtb_ext.BidType
+ var adm string
+ for _, v := range internalRequest.Imp {
+ adgExt, err := unmarshalExtImpAdgeneration(&v)
+ if err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: err.Error(),
+ },
+ }
+ }
+ if adgExt.Id == bidResp.Locationid {
+ impId = v.ID
+ bitType = openrtb_ext.BidTypeBanner
+ adm = createAd(&bidResp, impId)
+ bid := openrtb.Bid{
+ ID: bidResp.Locationid,
+ ImpID: impId,
+ AdM: adm,
+ Price: bidResp.Cpm,
+ W: bidResp.W,
+ H: bidResp.H,
+ CrID: bidResp.Creativeid,
+ DealID: bidResp.Dealid,
+ }
+
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: bitType,
+ })
+ return bidResponse, nil
+ }
+ }
+ return nil, nil
+}
+
+func createAd(body *adgServerResponse, impId string) string {
+ ad := body.Ad
+ if body.Vastxml != "" {
+ ad = "
" + insertVASTMethod(impId, body.Vastxml) + ""
+ }
+ ad = appendChildToBody(ad, body.Beacon)
+ unwrappedAd := removeWrapper(ad)
+ if unwrappedAd != "" {
+ return unwrappedAd
+ }
+ return ad
+}
+
+func insertVASTMethod(bidId string, vastxml string) string {
+ rep := regexp.MustCompile(`/\r?\n/g`)
+ var replacedVastxml = rep.ReplaceAllString(vastxml, "")
+ return ""
+}
+
+func appendChildToBody(ad string, data string) string {
+ rep := regexp.MustCompile(`<\/\s?body>`)
+ return rep.ReplaceAllString(ad, data+"")
+ lastBodyIndex := strings.LastIndex(ad, "", "", 1), "\n\n\n
\n\n\n\n
\n")
+}
+
+func removeWrapper(ad string) string {
+ bodyIndex := strings.Index(ad, "
")
+ if bodyIndex == -1 || lastBodyIndex == -1 {
+ return ""
+ }
+
+ str := strings.TrimSpace(strings.Replace(strings.Replace(ad[bodyIndex:lastBodyIndex], "
", "", 1))
+ return str
+}
+
+func NewAdgenerationAdapter(endpoint string) *AdgenerationAdapter {
+ return &AdgenerationAdapter{
+ endpoint,
+ "1.0.0",
+ "JPY",
+ }
+}
diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go
new file mode 100644
index 00000000000..2c679e10471
--- /dev/null
+++ b/adapters/adgeneration/adgeneration_test.go
@@ -0,0 +1,176 @@
+package adgeneration
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "adgenerationtest", NewAdgenerationAdapter("https://d.socdm.com/adsv/v1"))
+}
+
+func TestgetRequestUri(t *testing.T) {
+ bidder := NewAdgenerationAdapter("https://d.socdm.com/adsv/v1")
+ // Test items
+ failedRequest := &openrtb.BidRequest{
+ ID: "test-failed-bid-request",
+ Imp: []openrtb.Imp{
+ {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{{ "id": "58278" }}`)},
+ {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"_bidder": { "id": "58278" }}`)},
+ {ID: "extImpAdgeneration-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "_id": "58278" }}`)},
+ },
+ Device: &openrtb.Device{UA: "testUA", IP: "testIP"},
+ Site: &openrtb.Site{Page: "https://supership.com"},
+ User: &openrtb.User{BuyerUID: "buyerID"},
+ }
+ successRequest := &openrtb.BidRequest{
+ ID: "test-success-bid-request",
+ Imp: []openrtb.Imp{
+ {ID: "bidRequest-success-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)},
+ },
+ Device: &openrtb.Device{UA: "testUA", IP: "testIP"},
+ Site: &openrtb.Site{Page: "https://supership.com"},
+ User: &openrtb.User{BuyerUID: "buyerID"},
+ }
+
+ numRequests := len(failedRequest.Imp)
+ for index := 0; index < numRequests; index++ {
+ httpRequests, err := bidder.getRequestUri(failedRequest, index)
+ if err == nil {
+ t.Errorf("getRequestUri: %v did not throw an error", failedRequest.Imp[index])
+ }
+ if httpRequests != "" {
+ t.Errorf("getRequestUri: %v did return Request: %s", failedRequest.Imp[index], httpRequests)
+ }
+ }
+ numRequests = len(successRequest.Imp)
+ for index := 0; index < numRequests; index++ {
+ // RequestUri Test.
+ httpRequests, err := bidder.getRequestUri(successRequest, index)
+ if err != nil {
+ t.Errorf("getRequestUri: %v did throw an error: %v", successRequest.Imp[index], err)
+ }
+ if httpRequests == "adapterver="+bidder.version+"¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html" {
+ t.Errorf("getRequestUri: %v did return Request: %s", successRequest.Imp[index], httpRequests)
+ }
+ // getRawQuery Test.
+ adgExt, err := unmarshalExtImpAdgeneration(&successRequest.Imp[index])
+ if err != nil {
+ t.Errorf("unmarshalExtImpAdgeneration: %v did throw an error: %v", successRequest.Imp[index], err)
+ }
+ rawQuery := bidder.getRawQuery(adgExt.Id, successRequest, &successRequest.Imp[index])
+ expectQueries := map[string]string{
+ "posall": "SSPLOC",
+ "id": adgExt.Id,
+ "sdktype": "0",
+ "hb": "true",
+ "currency": bidder.getCurrency(successRequest),
+ "sdkname": "prebidserver",
+ "adapterver": bidder.version,
+ "size": getSizes(&successRequest.Imp[index]),
+ "tp": successRequest.Site.Name,
+ }
+ for key, expectedValue := range expectQueries {
+ actualValue := rawQuery.Get(key)
+ if actualValue == "" {
+ if !(key == "size" || key == "tp") {
+ t.Errorf("getRawQuery: key %s is required value.", key)
+ }
+ }
+ if actualValue != expectedValue {
+ t.Errorf("getRawQuery: %s value does not match expected %s, actual %s", key, expectedValue, actualValue)
+ }
+ }
+ }
+}
+
+func TestGetSizes(t *testing.T) {
+ // Test items
+ var request *openrtb.Imp
+ var size string
+ multiFormatBanner := &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 320, H: 50}}}
+ noFormatBanner := &openrtb.Banner{Format: []openrtb.Format{}}
+ nativeFormat := &openrtb.Native{}
+
+ request = &openrtb.Imp{Banner: multiFormatBanner}
+ size = getSizes(request)
+ if size != "300×250,320×50" {
+ t.Errorf("%v does not match size.", multiFormatBanner)
+ }
+ request = &openrtb.Imp{Banner: noFormatBanner}
+ size = getSizes(request)
+ if size != "" {
+ t.Errorf("%v does not match size.", noFormatBanner)
+ }
+ request = &openrtb.Imp{Native: nativeFormat}
+ size = getSizes(request)
+ if size != "" {
+ t.Errorf("%v does not match size.", nativeFormat)
+ }
+}
+
+func TestGetCurrency(t *testing.T) {
+ bidder := NewAdgenerationAdapter("https://d.socdm.com/adsv/v1")
+ // Test items
+ var request *openrtb.BidRequest
+ var currency string
+ innerDefaultCur := []string{"USD", "JPY"}
+ usdCur := []string{"USD", "EUR"}
+
+ request = &openrtb.BidRequest{Cur: innerDefaultCur}
+ currency = bidder.getCurrency(request)
+ if currency != "JPY" {
+ t.Errorf("%v does not match currency.", innerDefaultCur)
+ }
+ request = &openrtb.BidRequest{Cur: usdCur}
+ currency = bidder.getCurrency(request)
+ if currency != "USD" {
+ t.Errorf("%v does not match currency.", usdCur)
+ }
+}
+
+func TestCreateAd(t *testing.T) {
+ // Test items
+ adgBannerImpId := "test-banner-imp"
+ adgBannerResponse := adgServerResponse{
+ Ad: "\n
\n\n\n
\n",
+ Beacon: "",
+ Beaconurl: "https://dummy-beacon.com",
+ Cpm: 50,
+ Creativeid: "DummyDsp_SdkTeam_supership.jp",
+ H: 300,
+ W: 250,
+ Ttl: 10,
+ LandingUrl: "",
+ Scheduleid: "111111",
+ }
+ matchBannerTag := "
\n\n
\n"
+
+ adgVastImpId := "test-vast-imp"
+ adgVastResponse := adgServerResponse{
+ Ad: "\n
\n\n\n
\n",
+ Beacon: "",
+ Beaconurl: "https://dummy-beacon.com",
+ Cpm: 50,
+ Creativeid: "DummyDsp_SdkTeam_supership.jp",
+ H: 300,
+ W: 250,
+ Ttl: 10,
+ LandingUrl: "",
+ Vastxml: "",
+ Scheduleid: "111111",
+ }
+ matchVastTag := ""
+
+ bannerAd := createAd(&adgBannerResponse, adgBannerImpId)
+ if bannerAd != matchBannerTag {
+ t.Errorf("%v does not match createAd.", adgBannerResponse)
+ }
+ vastAd := createAd(&adgVastResponse, adgVastImpId)
+ if vastAd != matchVastTag {
+ t.Errorf("%v does not match createAd.", adgVastResponse)
+ }
+}
diff --git a/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json
new file mode 100644
index 00000000000..d23a510bee5
--- /dev/null
+++ b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json
@@ -0,0 +1,151 @@
+{
+ "mockBidRequest":{
+ "id": "some-request-id",
+ "site": {
+ "page": "http://example.com/test.html"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "id": "58278"
+ }
+ }
+ }
+ ],
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "internalRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "http://example.com/test.html"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "id": "58278"
+ }
+ }
+ }
+ ],
+ "tmax": 500
+ },
+ "expectedRequest":{
+ "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
+ "headers": {
+ "Accept": [
+ "application/json"
+ ],
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ]
+ }
+ },
+ "mockResponse":{
+ "status": 200,
+ "body": {
+ "ad": "\n \n \n
+`
+
+type ResponseAdUnit struct {
+ ID string `json:"id"`
+ CrID string `json:"crid"`
+ Currency string `json:"currency"`
+ Price string `json:"price"`
+ Width string `json:"width"`
+ Height string `json:"height"`
+ Code string `json:"code"`
+ WinURL string `json:"winUrl"`
+ StatsURL string `json:"statsUrl"`
+ Error string `json:"error"`
+}
+
+type requestData struct {
+ Url *url.URL
+ Headers *http.Header
+ SlaveSizes map[string]string
+}
+
+func NewAdOceanBidder(client *http.Client, endpointTemplateString string) *AdOceanAdapter {
+ a := &adapters.HTTPAdapter{Client: client}
+ endpointTemplate, err := template.New("endpointTemplate").Parse(endpointTemplateString)
+ if err != nil {
+ glog.Fatal("Unable to parse endpoint template")
+ return nil
+ }
+
+ whiteSpace := regexp.MustCompile(`\s+`)
+
+ return &AdOceanAdapter{
+ http: a,
+ endpointTemplate: *endpointTemplate,
+ measurementCode: whiteSpace.ReplaceAllString(measurementCode, " "),
+ }
+}
+
+type AdOceanAdapter struct {
+ http *adapters.HTTPAdapter
+ endpointTemplate template.Template
+ measurementCode string
+}
+
+func (a *AdOceanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ if len(request.Imp) == 0 {
+ return nil, []error{&errortypes.BadInput{
+ Message: "No impression in the bid request",
+ }}
+ }
+
+ consentString := ""
+ if request.User != nil {
+ var extUser openrtb_ext.ExtUser
+ if err := json.Unmarshal(request.User.Ext, &extUser); err == nil {
+ consentString = extUser.Consent
+ }
+ }
+
+ var errors []error
+ var err error
+ requestsData := make([]*requestData, 0, len(request.Imp))
+ for _, auction := range request.Imp {
+ requestsData, err = a.addNewBid(requestsData, &auction, request, consentString)
+ if err != nil {
+ errors = append(errors, err)
+ }
+ }
+
+ httpRequests := make([]*adapters.RequestData, 0, len(requestsData))
+ for _, requestData := range requestsData {
+ httpRequests = append(httpRequests, &adapters.RequestData{
+ Method: "GET",
+ Uri: requestData.Url.String(),
+ Headers: *requestData.Headers,
+ })
+ }
+
+ return httpRequests, errors
+}
+
+func (a *AdOceanAdapter) addNewBid(
+ requestsData []*requestData,
+ imp *openrtb.Imp,
+ request *openrtb.BidRequest,
+ consentString string,
+) ([]*requestData, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return requestsData, &errortypes.BadInput{
+ Message: "Error parsing bidderExt object",
+ }
+ }
+
+ var adOceanExt openrtb_ext.ExtImpAdOcean
+ if err := json.Unmarshal(bidderExt.Bidder, &adOceanExt); err != nil {
+ return requestsData, &errortypes.BadInput{
+ Message: "Error parsing adOceanExt parameters",
+ }
+ }
+
+ addedToExistingRequest := addToExistingRequest(requestsData, &adOceanExt, imp, (request.Test == 1))
+ if addedToExistingRequest {
+ return requestsData, nil
+ }
+
+ slaveSizes := map[string]string{}
+ slaveSizes[adOceanExt.SlaveID] = getImpSizes(imp)
+
+ url, err := a.makeURL(&adOceanExt, imp, request, slaveSizes, consentString)
+ if err != nil {
+ return requestsData, err
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ if request.Device != nil {
+ headers.Add("User-Agent", request.Device.UA)
+
+ if request.Device.IP != "" {
+ headers.Add("X-Forwarded-For", request.Device.IP)
+ } else if request.Device.IPv6 != "" {
+ headers.Add("X-Forwarded-For", request.Device.IPv6)
+ }
+ }
+
+ if request.Site != nil {
+ headers.Add("Referer", request.Site.Page)
+ }
+
+ requestsData = append(requestsData, &requestData{
+ Url: url,
+ Headers: &headers,
+ SlaveSizes: slaveSizes,
+ })
+
+ return requestsData, nil
+}
+
+func addToExistingRequest(requestsData []*requestData, newParams *openrtb_ext.ExtImpAdOcean, imp *openrtb.Imp, testImp bool) bool {
+ auctionID := imp.ID
+
+ for _, requestData := range requestsData {
+ queryParams := requestData.Url.Query()
+ masterID := queryParams["id"][0]
+
+ if masterID == newParams.MasterID {
+ if _, has := requestData.SlaveSizes[newParams.SlaveID]; has {
+ continue
+ }
+
+ queryParams.Add("aid", newParams.SlaveID+":"+auctionID)
+ requestData.SlaveSizes[newParams.SlaveID] = getImpSizes(imp)
+ setSlaveSizesParam(&queryParams, requestData.SlaveSizes, testImp)
+
+ newUrl := *(requestData.Url)
+ newUrl.RawQuery = queryParams.Encode()
+ if len(newUrl.String()) < maxUriLength {
+ requestData.Url = &newUrl
+ return true
+ }
+
+ delete(requestData.SlaveSizes, newParams.SlaveID)
+ }
+ }
+
+ return false
+}
+
+func (a *AdOceanAdapter) makeURL(
+ params *openrtb_ext.ExtImpAdOcean,
+ imp *openrtb.Imp,
+ request *openrtb.BidRequest,
+ slaveSizes map[string]string,
+ consentString string,
+) (*url.URL, error) {
+ endpointParams := macros.EndpointTemplateParams{Host: params.EmitterDomain}
+ host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams)
+ if err != nil {
+ return nil, &errortypes.BadInput{
+ Message: "Unable to parse endpoint url template: " + err.Error(),
+ }
+ }
+
+ endpointURL, err := url.Parse(host)
+ if err != nil {
+ return nil, &errortypes.BadInput{
+ Message: "Malformed URL: " + err.Error(),
+ }
+ }
+
+ randomizedPart := 10000000 + rand.Intn(99999999-10000000)
+ if request.Test == 1 {
+ randomizedPart = 10000000
+ }
+ endpointURL.Path = "/_" + strconv.Itoa(randomizedPart) + "/ad.json"
+
+ auctionID := imp.ID
+ queryParams := url.Values{}
+ queryParams.Add("pbsrv_v", adapterVersion)
+ queryParams.Add("id", params.MasterID)
+ queryParams.Add("nc", "1")
+ queryParams.Add("nosecure", "1")
+ queryParams.Add("aid", params.SlaveID+":"+auctionID)
+ if consentString != "" {
+ queryParams.Add("gdpr_consent", consentString)
+ queryParams.Add("gdpr", "1")
+ }
+ if request.User != nil && request.User.BuyerUID != "" {
+ queryParams.Add("hcuserid", request.User.BuyerUID)
+ }
+
+ setSlaveSizesParam(&queryParams, slaveSizes, (request.Test == 1))
+ endpointURL.RawQuery = queryParams.Encode()
+
+ return endpointURL, nil
+}
+
+func getImpSizes(imp *openrtb.Imp) string {
+ if imp.Banner == nil {
+ return ""
+ }
+
+ if len(imp.Banner.Format) > 0 {
+ sizes := make([]string, len(imp.Banner.Format))
+ for i, format := range imp.Banner.Format {
+ sizes[i] = strconv.FormatUint(format.W, 10) + "x" + strconv.FormatUint(format.H, 10)
+ }
+
+ return strings.Join(sizes, "_")
+ }
+
+ if imp.Banner.W != nil && imp.Banner.H != nil {
+ return strconv.FormatUint(*imp.Banner.W, 10) + "x" + strconv.FormatUint(*imp.Banner.H, 10)
+ }
+
+ return ""
+}
+
+func setSlaveSizesParam(queryParams *url.Values, slaveSizes map[string]string, orderByKey bool) {
+ sizeValues := make([]string, 0, len(slaveSizes))
+ slaveIDs := make([]string, 0, len(slaveSizes))
+ for k := range slaveSizes {
+ slaveIDs = append(slaveIDs, k)
+ }
+
+ if orderByKey {
+ sort.Strings(slaveIDs)
+ }
+
+ for _, slaveID := range slaveIDs {
+ sizes := slaveSizes[slaveID]
+ if sizes == "" {
+ continue
+ }
+
+ rawSlaveID := strings.Replace(slaveID, "adocean", "", 1)
+ sizeValues = append(sizeValues, rawSlaveID+"~"+sizes)
+ }
+
+ if len(sizeValues) > 0 {
+ queryParams.Set("aosspsizes", strings.Join(sizeValues, "-"))
+ }
+}
+
+func (a *AdOceanAdapter) MakeBids(
+ internalRequest *openrtb.BidRequest,
+ externalRequest *adapters.RequestData,
+ response *adapters.ResponseData,
+) (*adapters.BidderResponse, []error) {
+ if response.StatusCode != http.StatusOK {
+ return nil, []error{fmt.Errorf("Unexpected status code: %d. Network error?", response.StatusCode)}
+ }
+
+ requestURL, _ := url.Parse(externalRequest.Uri)
+ queryParams := requestURL.Query()
+ auctionIDs := queryParams["aid"]
+
+ bidResponses := make([]ResponseAdUnit, 0)
+ if err := json.Unmarshal(response.Body, &bidResponses); err != nil {
+ return nil, []error{err}
+ }
+
+ var parsedResponses = adapters.NewBidderResponseWithBidsCapacity(len(auctionIDs))
+ var errors []error
+ var slaveToAuctionIDMap = make(map[string]string, len(auctionIDs))
+
+ for _, auctionFullID := range auctionIDs {
+ auctionIDsSlice := strings.SplitN(auctionFullID, ":", 2)
+ slaveToAuctionIDMap[auctionIDsSlice[0]] = auctionIDsSlice[1]
+ }
+
+ for _, bid := range bidResponses {
+ if auctionID, found := slaveToAuctionIDMap[bid.ID]; found {
+ if bid.Error == "true" {
+ continue
+ }
+
+ price, _ := strconv.ParseFloat(bid.Price, 64)
+ width, _ := strconv.ParseUint(bid.Width, 10, 64)
+ height, _ := strconv.ParseUint(bid.Height, 10, 64)
+ adCode, err := a.prepareAdCodeForBid(bid)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ parsedResponses.Bids = append(parsedResponses.Bids, &adapters.TypedBid{
+ Bid: &openrtb.Bid{
+ ID: bid.ID,
+ ImpID: auctionID,
+ Price: price,
+ AdM: adCode,
+ CrID: bid.CrID,
+ W: width,
+ H: height,
+ },
+ BidType: openrtb_ext.BidTypeBanner,
+ })
+ parsedResponses.Currency = bid.Currency
+ }
+ }
+
+ return parsedResponses, errors
+}
+
+func (a *AdOceanAdapter) prepareAdCodeForBid(bid ResponseAdUnit) (string, error) {
+ sspCode, err := url.QueryUnescape(bid.Code)
+ if err != nil {
+ return "", err
+ }
+
+ adCode := fmt.Sprintf(a.measurementCode, bid.WinURL, bid.StatsURL) + sspCode
+
+ return adCode, nil
+}
diff --git a/adapters/adocean/adocean_test.go b/adapters/adocean/adocean_test.go
new file mode 100644
index 00000000000..1088fedd30e
--- /dev/null
+++ b/adapters/adocean/adocean_test.go
@@ -0,0 +1,12 @@
+package adocean
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "adoceantest", NewAdOceanBidder(new(http.Client), "https://{{.Host}}"))
+}
diff --git a/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json b/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json
new file mode 100644
index 00000000000..9e4ce06e83a
--- /dev/null
+++ b/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json
@@ -0,0 +1,133 @@
+{
+ "mockBidRequest": {
+ "id": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b",
+ "source": {
+ "tid": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b"
+ },
+ "tmax": 1000,
+ "imp": [{
+ "id": "ao-test",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaozpniqismex"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }, {
+ "w": 320,
+ "h": 600
+ }]
+ }
+ }, {
+ "id": "secod-twelve",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaowafpdwlrks"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ }
+ }],
+ "test": 1,
+ "ext": {
+ "prebid": {
+ "targeting": {
+ "includewinners": true,
+ "includebidderkeys": false
+ }
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://192.168.100.203/testing/prebid_server/test.html"
+ },
+ "device": {
+ "w": 418,
+ "h": 961
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw"
+ }
+ }
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Asecod-twelve&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250_320x600&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [{
+ "id": "adoceanmyaozpniqismex",
+ "price": "1",
+ "winurl": "https://win-url.com",
+ "statsUrl": "https://stats-url.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ },
+ {
+ "id": "adoceanmyaowafpdwlrks",
+ "price": "1",
+ "winurl": "https://win-url2.com",
+ "statsUrl": "https://stats-url2.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ }
+ ]
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "adoceanmyaozpniqismex",
+ "impid": "ao-test",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ },{
+ "bid": {
+ "id": "adoceanmyaowafpdwlrks",
+ "impid": "secod-twelve",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
diff --git a/adapters/adocean/adoceantest/exemplary/single-banner-impression.json b/adapters/adocean/adoceantest/exemplary/single-banner-impression.json
new file mode 100644
index 00000000000..e6d70f840aa
--- /dev/null
+++ b/adapters/adocean/adoceantest/exemplary/single-banner-impression.json
@@ -0,0 +1,116 @@
+{
+ "mockBidRequest": {
+ "id": "9ed903f4-383d-406b-8011-4f06526cb02c",
+ "source": {
+ "tid": "9ed903f4-383d-406b-8011-4f06526cb02c"
+ },
+ "tmax": 1000,
+ "imp": [
+ {
+ "id": "ao-test",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaozpniqismex"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "test": 1,
+ "ext": {
+ "prebid": {
+ "targeting": {
+ "includewinners": true,
+ "includebidderkeys": false
+ }
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://example.com/test.html"
+ },
+ "device": {
+ "w": 1280,
+ "h": 720,
+ "ip": "192.168.1.1"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [
+ {
+ "id": "adoceanmyaozpniqismex",
+ "price": "1",
+ "winurl": "https://win-url.com",
+ "statsUrl": "https://stats-url.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ },
+ {
+ "id": "adoceanmyaowafpdwlrks",
+ "price": "1",
+ "winurl": "",
+ "statsUrl": "",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ }
+ ]
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "id": "adoceanmyaozpniqismex",
+ "impid": "ao-test",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/adocean/adoceantest/params/race/banner.json b/adapters/adocean/adoceantest/params/race/banner.json
new file mode 100644
index 00000000000..f9f38481350
--- /dev/null
+++ b/adapters/adocean/adoceantest/params/race/banner.json
@@ -0,0 +1,5 @@
+{
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaozpniqismex"
+}
diff --git a/adapters/adocean/adoceantest/supplemental/bad-response.json b/adapters/adocean/adoceantest/supplemental/bad-response.json
new file mode 100644
index 00000000000..e7b871f9a09
--- /dev/null
+++ b/adapters/adocean/adoceantest/supplemental/bad-response.json
@@ -0,0 +1,66 @@
+{
+ "mockBidRequest": {
+ "id": "9ed903f4-383d-406b-8011-4f06526cb02c",
+ "source": {
+ "tid": "9ed903f4-383d-406b-8011-4f06526cb02c"
+ },
+ "tmax": 1000,
+ "imp": [
+ {
+ "id": "ao-test",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaozpniqismex"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "test": 1,
+ "ext": {
+ "prebid": {
+ "targeting": {
+ "includewinners": true,
+ "includebidderkeys": false
+ }
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://example.com/test.html"
+ },
+ "device": {
+ "w": 1280,
+ "h": 720,
+ "ip": "192.168.1.1"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": "{ key: nil }"
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "json: cannot unmarshal string into Go value of type []adocean.ResponseAdUnit",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adocean/adoceantest/supplemental/encode-error.json b/adapters/adocean/adoceantest/supplemental/encode-error.json
new file mode 100644
index 00000000000..8dfd0f83e66
--- /dev/null
+++ b/adapters/adocean/adoceantest/supplemental/encode-error.json
@@ -0,0 +1,80 @@
+{
+ "mockBidRequest": {
+ "id": "9ed903f4-383d-406b-8011-4f06526cb02c",
+ "source": {
+ "tid": "9ed903f4-383d-406b-8011-4f06526cb02c"
+ },
+ "tmax": 1000,
+ "imp": [
+ {
+ "id": "ao-test",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaozpniqismex"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "test": 1,
+ "ext": {
+ "prebid": {
+ "targeting": {
+ "includewinners": true,
+ "includebidderkeys": false
+ }
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://example.com/test.html"
+ },
+ "device": {
+ "w": 1280,
+ "h": 720,
+ "ip": "192.168.1.1"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [
+ {
+ "id": "adoceanmyaozpniqismex",
+ "price": "1",
+ "winurl": "",
+ "statsUrl": "",
+ "code": " %a",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ }
+ ]
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "invalid URL escape \"%a\"",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adocean/adoceantest/supplemental/network-error.json b/adapters/adocean/adoceantest/supplemental/network-error.json
new file mode 100644
index 00000000000..54e528af369
--- /dev/null
+++ b/adapters/adocean/adoceantest/supplemental/network-error.json
@@ -0,0 +1,66 @@
+{
+ "mockBidRequest": {
+ "id": "9ed903f4-383d-406b-8011-4f06526cb02c",
+ "source": {
+ "tid": "9ed903f4-383d-406b-8011-4f06526cb02c"
+ },
+ "tmax": 1000,
+ "imp": [
+ {
+ "id": "ao-test",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaozpniqismex"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "test": 1,
+ "ext": {
+ "prebid": {
+ "targeting": {
+ "includewinners": true,
+ "includebidderkeys": false
+ }
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://example.com/test.html"
+ },
+ "device": {
+ "w": 1280,
+ "h": 720,
+ "ip": "192.168.1.1"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 500,
+ "body": {}
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 500. Network error?",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adocean/adoceantest/supplemental/no-bid.json b/adapters/adocean/adoceantest/supplemental/no-bid.json
new file mode 100644
index 00000000000..625fb78f3f6
--- /dev/null
+++ b/adapters/adocean/adoceantest/supplemental/no-bid.json
@@ -0,0 +1,159 @@
+{
+ "mockBidRequest": {
+ "id": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b",
+ "source": {
+ "tid": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b"
+ },
+ "tmax": 1000,
+ "imp": [{
+ "id": "ao-test",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaozpniqismex"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ }
+ }, {
+ "id": "ao-test-two",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaowafpdwlrks"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ }
+ }, {
+ "id": "ao-test-three",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaowafpdwlrks"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ }
+ }],
+ "test": 1,
+ "ext": {
+ "prebid": {
+ "targeting": {
+ "includewinners": true,
+ "includebidderkeys": false
+ }
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://localhost/prebid_server/test.html"
+ },
+ "device": {
+ "w": 418,
+ "h": 961
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw"
+ }
+ }
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [{
+ "id": "adoceanmyaozpniqismex",
+ "error": "true"
+ },
+ {
+ "id": "adoceanmyaowafpdwlrks",
+ "price": "1",
+ "winurl": "https://win-url2.com",
+ "statsUrl": "https://stats-url2.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ }
+ ]
+ }
+ }, {
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [{
+ "id": "adoceanmyaowafpdwlrks",
+ "price": "1",
+ "winurl": "https://win-url3.com",
+ "statsUrl": "https://stats-url3.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ }]
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "adoceanmyaowafpdwlrks",
+ "impid": "ao-test-two",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }, {
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "adoceanmyaowafpdwlrks",
+ "impid": "ao-test-three",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
diff --git a/adapters/adocean/adoceantest/supplemental/no-impression.json b/adapters/adocean/adoceantest/supplemental/no-impression.json
new file mode 100644
index 00000000000..8f2a8eef351
--- /dev/null
+++ b/adapters/adocean/adoceantest/supplemental/no-impression.json
@@ -0,0 +1,36 @@
+{
+ "mockBidRequest": {
+ "id": "9ed903f4-383d-406b-8011-4f06526cb02c",
+ "source": {
+ "tid": "9ed903f4-383d-406b-8011-4f06526cb02c"
+ },
+ "tmax": 1000,
+ "imp": [],
+ "test": 1,
+ "ext": {
+ "prebid": {
+ "targeting": {
+ "includewinners": true,
+ "includebidderkeys": false
+ }
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://example.com/test.html"
+ },
+ "device": {
+ "w": 1280,
+ "h": 720,
+ "ip": "192.168.1.1"
+ }
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "No impression in the bid request",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adocean/adoceantest/supplemental/no-sizes.json b/adapters/adocean/adoceantest/supplemental/no-sizes.json
new file mode 100644
index 00000000000..6286d805477
--- /dev/null
+++ b/adapters/adocean/adoceantest/supplemental/no-sizes.json
@@ -0,0 +1,168 @@
+{
+ "mockBidRequest": {
+ "id": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b",
+ "source": {
+ "tid": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b"
+ },
+ "tmax": 1000,
+ "imp": [{
+ "id": "ao-test",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaozpniqismex"
+ }
+ }
+ }, {
+ "id": "ao-test-two",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaowafpdwlrks"
+ }
+ },
+ "banner": {
+ "format": []
+ }
+ }, {
+ "id": "ao-test-three",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaowafpdwlrks"
+ }
+ },
+ "banner": {
+ "w": 300,
+ "h": 250
+ }
+ }],
+ "test": 1,
+ "ext": {
+ "prebid": {
+ "targeting": {
+ "includewinners": true,
+ "includebidderkeys": false
+ }
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://localhost/prebid_server/test.html"
+ },
+ "device": {
+ "w": 418,
+ "h": 961
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw"
+ }
+ }
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [{
+ "id": "adoceanmyaozpniqismex",
+ "price": "1",
+ "winurl": "https://win-url.com",
+ "statsUrl": "https://stats-url.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ },
+ {
+ "id": "adoceanmyaowafpdwlrks",
+ "price": "1",
+ "winurl": "https://win-url2.com",
+ "statsUrl": "https://stats-url2.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ }
+ ]
+ }
+ }, {
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [{
+ "id": "adoceanmyaowafpdwlrks",
+ "price": "1",
+ "winurl": "https://win-url3.com",
+ "statsUrl": "https://stats-url3.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ }]
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "adoceanmyaozpniqismex",
+ "impid": "ao-test",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }, {
+ "bid": {
+ "id": "adoceanmyaowafpdwlrks",
+ "impid": "ao-test-two",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }, {
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "adoceanmyaowafpdwlrks",
+ "impid": "ao-test-three",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
diff --git a/adapters/adocean/adoceantest/supplemental/requests-merge.json b/adapters/adocean/adoceantest/supplemental/requests-merge.json
new file mode 100644
index 00000000000..e0736ec918f
--- /dev/null
+++ b/adapters/adocean/adoceantest/supplemental/requests-merge.json
@@ -0,0 +1,179 @@
+{
+ "mockBidRequest": {
+ "id": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b",
+ "source": {
+ "tid": "b5300274-a7ec-4cdb-bf5b-d75eeb481a6b"
+ },
+ "tmax": 1000,
+ "imp": [{
+ "id": "ao-test",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaozpniqismex"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ }
+ }, {
+ "id": "ao-test-two",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaowafpdwlrks"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ }
+ }, {
+ "id": "ao-test-three",
+ "ext": {
+ "bidder": {
+ "emiter": "myao.adocean.pl",
+ "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7",
+ "slaveId": "adoceanmyaowafpdwlrks"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ }
+ }],
+ "test": 1,
+ "ext": {
+ "prebid": {
+ "targeting": {
+ "includewinners": true,
+ "includebidderkeys": false
+ }
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://localhost/prebid_server/test.html"
+ },
+ "device": {
+ "w": 418,
+ "h": 961
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw"
+ }
+ }
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [{
+ "id": "adoceanmyaozpniqismex",
+ "price": "1",
+ "winurl": "https://win-url.com",
+ "statsUrl": "https://stats-url.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ },
+ {
+ "id": "adoceanmyaowafpdwlrks",
+ "price": "1",
+ "winurl": "https://win-url2.com",
+ "statsUrl": "https://stats-url2.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ }
+ ]
+ }
+ }, {
+ "expectedRequest": {
+ "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.1.0"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [{
+ "id": "adoceanmyaowafpdwlrks",
+ "price": "1",
+ "winurl": "https://win-url3.com",
+ "statsUrl": "https://stats-url3.com",
+ "code": " ",
+ "currency": "EUR",
+ "minFloorPrice": "0.01",
+ "width": "300",
+ "height": "250",
+ "crid": "0af345b42983cc4bc0",
+ "ttl": "300"
+ }]
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "adoceanmyaozpniqismex",
+ "impid": "ao-test",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }, {
+ "bid": {
+ "id": "adoceanmyaowafpdwlrks",
+ "impid": "ao-test-two",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }, {
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "adoceanmyaowafpdwlrks",
+ "impid": "ao-test-three",
+ "price": 1,
+ "adm": " ",
+ "crid": "0af345b42983cc4bc0",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
diff --git a/adapters/adocean/params_test.go b/adapters/adocean/params_test.go
new file mode 100644
index 00000000000..91e2fbdcb67
--- /dev/null
+++ b/adapters/adocean/params_test.go
@@ -0,0 +1,50 @@
+package adocean
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, validParam := range validParams {
+ if err := validator.Validate(openrtb_ext.BidderAdOcean, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected adocean params: %s", validParam)
+ }
+ }
+}
+
+func TestInvalidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, invalidParam := range invalidParams {
+ if err := validator.Validate(openrtb_ext.BidderAdOcean, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmyaozpniqismex"}`,
+}
+
+var invalidParams = []string{
+ `{}`,
+ `{"masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmyaozpniqismex"}`,
+ `{"emiter": "myao.adocean.pl", "slaveId": "adoceanmyaozpniqismex"}`,
+ `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7"}`,
+ `{"emiter": "", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmyaozpniqismex"}`,
+ `{"emiter": "myao.adocean.pl", "", "slaveId": "adoceanmyaozpniqismex"}`,
+ `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": ""}`,
+ `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7Z utQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmyaozpniqismex"}`,
+ `{"emiter": "myao.adocean.pl", "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", "slaveId": "adoceanmy iqismex"}`,
+}
diff --git a/adapters/adocean/usersync.go b/adapters/adocean/usersync.go
new file mode 100644
index 00000000000..4bfe39e11e5
--- /dev/null
+++ b/adapters/adocean/usersync.go
@@ -0,0 +1,12 @@
+package adocean
+
+import (
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/usersync"
+)
+
+func NewAdOceanSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("adocean", 328, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/adocean/usersync_test.go b/adapters/adocean/usersync_test.go
new file mode 100644
index 00000000000..aa0bcb77e21
--- /dev/null
+++ b/adapters/adocean/usersync_test.go
@@ -0,0 +1,34 @@
+package adocean
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAdOceanSyncer(t *testing.T) {
+ syncURL := "https://sync-host.com/redataredir/?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&url=localhost%2Fsetuid%3Fbidder%3Dadocean%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3DUUID"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewAdOceanSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "1",
+ Consent: "consent-string",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(
+ t,
+ "https://sync-host.com/redataredir/?gdpr=1&gdpr_consent=consent-string&url=localhost%2Fsetuid%3Fbidder%3Dadocean%26gdpr%3D1%26gdpr_consent%3Dconsent-string%26uid%3DUUID",
+ syncInfo.URL,
+ )
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.EqualValues(t, 328, syncer.GDPRVendorID())
+}
diff --git a/adapters/adoppler/adoppler.go b/adapters/adoppler/adoppler.go
new file mode 100644
index 00000000000..b37aa051363
--- /dev/null
+++ b/adapters/adoppler/adoppler.go
@@ -0,0 +1,210 @@
+package adoppler
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+var bidHeaders http.Header = map[string][]string{
+ "Accept": {"application/json"},
+ "Content-Type": {"application/json;charset=utf-8"},
+ "X-OpenRTB-Version": {"2.5"},
+}
+
+type adsVideoExt struct {
+ Duration int `json:"duration"`
+}
+
+type adsImpExt struct {
+ Video *adsVideoExt `json:"video"`
+}
+
+type AdopplerAdapter struct {
+ endpoint string
+}
+
+func NewAdopplerBidder(endpoint string) *AdopplerAdapter {
+ return &AdopplerAdapter{endpoint}
+}
+
+func (ads *AdopplerAdapter) MakeRequests(
+ req *openrtb.BidRequest,
+ info *adapters.ExtraRequestInfo,
+) (
+ []*adapters.RequestData,
+ []error,
+) {
+ if len(req.Imp) == 0 {
+ return nil, nil
+ }
+
+ var datas []*adapters.RequestData
+ var errs []error
+ for _, imp := range req.Imp {
+ ext, err := unmarshalExt(imp.Ext)
+ if err != nil {
+ errs = append(errs, &errortypes.BadInput{err.Error()})
+ continue
+ }
+
+ var r openrtb.BidRequest = *req
+ r.ID = req.ID + "-" + ext.AdUnit
+ r.Imp = []openrtb.Imp{imp}
+
+ body, err := json.Marshal(r)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ uri := fmt.Sprintf("%s/processHeaderBid/%s",
+ ads.endpoint, url.PathEscape(ext.AdUnit))
+ data := &adapters.RequestData{
+ Method: "POST",
+ Uri: uri,
+ Body: body,
+ Headers: bidHeaders,
+ }
+ datas = append(datas, data)
+ }
+
+ return datas, errs
+}
+
+func (ads *AdopplerAdapter) MakeBids(
+ intReq *openrtb.BidRequest,
+ extReq *adapters.RequestData,
+ resp *adapters.ResponseData,
+) (
+ *adapters.BidderResponse,
+ []error,
+) {
+ if resp.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+ if resp.StatusCode == http.StatusBadRequest {
+ return nil, []error{&errortypes.BadInput{"bad request"}}
+ }
+ if resp.StatusCode != http.StatusOK {
+ err := &errortypes.BadServerResponse{
+ fmt.Sprintf("unexpected status: %d", resp.StatusCode),
+ }
+ return nil, []error{err}
+ }
+
+ var bidResp openrtb.BidResponse
+ err := json.Unmarshal(resp.Body, &bidResp)
+ if err != nil {
+ err := &errortypes.BadServerResponse{
+ fmt.Sprintf("invalid body: %s", err.Error()),
+ }
+ return nil, []error{err}
+ }
+
+ impTypes := make(map[string]openrtb_ext.BidType)
+ for _, imp := range intReq.Imp {
+ if _, ok := impTypes[imp.ID]; ok {
+ return nil, []error{&errortypes.BadInput{
+ fmt.Sprintf("duplicate $.imp.id %s", imp.ID),
+ }}
+ }
+ if imp.Banner != nil {
+ impTypes[imp.ID] = openrtb_ext.BidTypeBanner
+ } else if imp.Video != nil {
+ impTypes[imp.ID] = openrtb_ext.BidTypeVideo
+ } else if imp.Audio != nil {
+ impTypes[imp.ID] = openrtb_ext.BidTypeAudio
+ } else if imp.Native != nil {
+ impTypes[imp.ID] = openrtb_ext.BidTypeNative
+ } else {
+ return nil, []error{&errortypes.BadInput{
+ "one of $.imp.banner, $.imp.video, $.imp.audio and $.imp.native field required",
+ }}
+ }
+ }
+
+ var bids []*adapters.TypedBid
+ for _, seatBid := range bidResp.SeatBid {
+ for _, bid := range seatBid.Bid {
+ tp, ok := impTypes[bid.ImpID]
+ if !ok {
+ err := &errortypes.BadServerResponse{
+ fmt.Sprintf("unknown impid: %s", bid.ImpID),
+ }
+ return nil, []error{err}
+ }
+
+ var bidVideo *openrtb_ext.ExtBidPrebidVideo
+ if tp == openrtb_ext.BidTypeVideo {
+ adsExt, err := unmarshalAdsExt(bid.Ext)
+ if err != nil {
+ return nil, []error{&errortypes.BadServerResponse{err.Error()}}
+ }
+ if adsExt == nil || adsExt.Video == nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ "$.seatbid.bid.ext.ads.video required",
+ }}
+ }
+ bidVideo = &openrtb_ext.ExtBidPrebidVideo{
+ Duration: adsExt.Video.Duration,
+ PrimaryCategory: head(bid.Cat),
+ }
+ }
+ bids = append(bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: tp,
+ BidVideo: bidVideo,
+ })
+ }
+ }
+
+ adsResp := adapters.NewBidderResponseWithBidsCapacity(len(bids))
+ adsResp.Bids = bids
+
+ return adsResp, nil
+}
+
+func unmarshalExt(ext json.RawMessage) (*openrtb_ext.ExtImpAdoppler, error) {
+ var bext adapters.ExtImpBidder
+ err := json.Unmarshal(ext, &bext)
+ if err != nil {
+ return nil, err
+ }
+
+ var adsExt openrtb_ext.ExtImpAdoppler
+ err = json.Unmarshal(bext.Bidder, &adsExt)
+ if err != nil {
+ return nil, err
+ }
+
+ if adsExt.AdUnit == "" {
+ return nil, errors.New("$.imp.ext.adoppler.adunit required")
+ }
+
+ return &adsExt, nil
+}
+
+func unmarshalAdsExt(ext json.RawMessage) (*adsImpExt, error) {
+ var e struct {
+ Ads *adsImpExt `json:"ads"`
+ }
+ err := json.Unmarshal(ext, &e)
+
+ return e.Ads, err
+}
+
+func head(s []string) string {
+ if len(s) == 0 {
+ return ""
+ }
+
+ return s[0]
+}
diff --git a/adapters/adoppler/adoppler_test.go b/adapters/adoppler/adoppler_test.go
new file mode 100644
index 00000000000..c3287ed4adb
--- /dev/null
+++ b/adapters/adoppler/adoppler_test.go
@@ -0,0 +1,12 @@
+package adoppler
+
+import (
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder := NewAdopplerBidder("http://adoppler.com")
+ adapterstest.RunJSONBidderTest(t, "adopplertest", bidder)
+}
diff --git a/adapters/adoppler/adopplertest/exemplary/multibid.json b/adapters/adoppler/adopplertest/exemplary/multibid.json
new file mode 100644
index 00000000000..851f4c5b917
--- /dev/null
+++ b/adapters/adoppler/adopplertest/exemplary/multibid.json
@@ -0,0 +1,60 @@
+{"mockBidRequest": {"id": "req1",
+ "imp":[{"id": "imp1",
+ "banner": {"w": 100,
+ "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}},
+ {"id": "imp2",
+ "video": {"minduration": 120,
+ "mimes": ["video/mp4"]},
+ "ext": {"bidder": {"adunit": "unit2"}}},
+ {"id": "imp3",
+ "native": {"request": "{}"},
+ "ext": {"bidder": {"adunit": "unit3"}}}]},
+ "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1",
+ "body": {"id": "req1-unit1",
+ "imp": [{"id": "imp1",
+ "banner": {"w": 100, "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]}},
+ "mockResponse": {"status": 200,
+ "body": {"id": "req1-imp1-resp1",
+ "seatbid": [{"bid": [{"id": "req1-imp1-bid1",
+ "impid": "imp1",
+ "price": 0.12,
+ "adm": "a banner"}]}],
+ "cur": "USD"}}},
+ {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2",
+ "body": {"id": "req1-unit2",
+ "imp": [{"id": "imp2",
+ "video": {"minduration": 120,
+ "mimes": ["video/mp4"]},
+ "ext": {"bidder": {"adunit": "unit2"}}}]}},
+ "mockResponse": {"status": 200,
+ "body": {"id": "req1-imp2-resp2",
+ "seatbid": [{"bid": [{"id": "req1-imp2-bid1",
+ "impid": "imp2",
+ "price": 0.24,
+ "adm": "",
+ "cat": ["IAB1", "IAB2"],
+ "ext": {"ads": {"video": {"duration": 121}}}}]}],
+ "cur": "USD"}}},
+ {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit3",
+ "body": {"id": "req1-unit3",
+ "imp": [{"id": "imp3",
+ "native": {"request": "{}"},
+ "ext": {"bidder": {"adunit": "unit3"}}}]}},
+ "mockResponse": {"status": 204,
+ "body": ""}}],
+ "expectedBidResponses": [{"currency": "USD",
+ "bids": [{"bid": {"id": "req1-imp1-bid1",
+ "impid": "imp1",
+ "price": 0.12,
+ "adm": "a banner"},
+ "type": "banner"}]},
+ {"currency": "USD",
+ "bids": [{"bid": {"id": "req1-imp2-bid1",
+ "impid": "imp2",
+ "price": 0.24,
+ "adm": "",
+ "cat": ["IAB1", "IAB2"],
+ "ext": {"ads": {"video": {"duration": 121}}}},
+ "type": "video"}]}]}
diff --git a/adapters/adoppler/adopplertest/exemplary/no-bid.json b/adapters/adoppler/adopplertest/exemplary/no-bid.json
new file mode 100644
index 00000000000..0e0f13586a8
--- /dev/null
+++ b/adapters/adoppler/adopplertest/exemplary/no-bid.json
@@ -0,0 +1,13 @@
+{"mockBidRequest": {"id": "req1",
+ "imp":[{"id": "imp1",
+ "banner": {"w": 100,
+ "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]},
+ "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1",
+ "body": {"id": "req1-unit1",
+ "imp": [{"id": "imp1",
+ "banner": {"w": 100, "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]}},
+ "mockResponse": {"status": 204,
+ "body": ""}}],
+ "expectedBidResponses": []}
diff --git a/adapters/adoppler/adopplertest/supplemental/bad-request.json b/adapters/adoppler/adopplertest/supplemental/bad-request.json
new file mode 100644
index 00000000000..3bdd5a5544e
--- /dev/null
+++ b/adapters/adoppler/adopplertest/supplemental/bad-request.json
@@ -0,0 +1,15 @@
+{"mockBidRequest": {"id": "req1",
+ "imp":[{"id": "imp1",
+ "banner": {"w": 100,
+ "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]},
+ "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1",
+ "body": {"id": "req1-unit1",
+ "imp": [{"id": "imp1",
+ "banner": {"w": 100, "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]}},
+ "mockResponse": {"status": 400,
+ "body": ""}}],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [{"value": "bad request",
+ "comparison": "literal"}]}
diff --git a/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json b/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json
new file mode 100644
index 00000000000..4382e36c54e
--- /dev/null
+++ b/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json
@@ -0,0 +1,38 @@
+{"mockBidRequest": {"id": "req1",
+ "imp":[{"id": "imp1",
+ "banner": {"w": 100,
+ "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}},
+ {"id": "imp1",
+ "banner": {"w": 100,
+ "h": 200},
+ "ext": {"bidder": {"adunit": "unit2"}}}]},
+ "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1",
+ "body": {"id": "req1-unit1",
+ "imp": [{"id": "imp1",
+ "banner": {"w": 100, "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]}},
+ "mockResponse": {"status": 200,
+ "body": {"id": "req1-imp1-resp1",
+ "seatbid": [{"bid": [{"id": "req1-imp1-bid1",
+ "impid": "imp1",
+ "price": 0.12,
+ "adm": "a banner"}]}],
+ "cur": "USD"}}},
+ {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2",
+ "body": {"id": "req1-unit2",
+ "imp": [{"id": "imp1",
+ "banner": {"w": 100, "h": 200},
+ "ext": {"bidder": {"adunit": "unit2"}}}]}},
+ "mockResponse": {"status": 200,
+ "body": {"id": "req1-imp1-resp1",
+ "seatbid": [{"bid": [{"id": "req1-imp1-bid1",
+ "impid": "imp1",
+ "price": 0.12,
+ "adm": "a banner"}]}],
+ "cur": "USD"}}}],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [{"value": "duplicate $.imp.id imp1",
+ "comparison": "literal"},
+ {"value": "duplicate $.imp.id imp1",
+ "comparison": "literal"}]}
diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-impid.json b/adapters/adoppler/adopplertest/supplemental/invalid-impid.json
new file mode 100644
index 00000000000..2e6ecf4a96c
--- /dev/null
+++ b/adapters/adoppler/adopplertest/supplemental/invalid-impid.json
@@ -0,0 +1,20 @@
+{"mockBidRequest": {"id": "req1",
+ "imp":[{"id": "imp1",
+ "banner": {"w": 100,
+ "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]},
+ "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1",
+ "body": {"id": "req1-unit1",
+ "imp": [{"id": "imp1",
+ "banner": {"w": 100, "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]}},
+ "mockResponse": {"status": 200,
+ "body": {"id": "req1-imp1-resp1",
+ "seatbid": [{"bid": [{"id": "req1-imp1-bid1",
+ "impid": "invalid",
+ "price": 0.12,
+ "adm": "a banner"}]}],
+ "cur": "USD"}}}],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [{"value": "unknown impid: invalid",
+ "comparison": "literal"}]}
diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-response.json b/adapters/adoppler/adopplertest/supplemental/invalid-response.json
new file mode 100644
index 00000000000..72420881aec
--- /dev/null
+++ b/adapters/adoppler/adopplertest/supplemental/invalid-response.json
@@ -0,0 +1,15 @@
+{"mockBidRequest": {"id": "req1",
+ "imp":[{"id": "imp1",
+ "banner": {"w": 100,
+ "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]},
+ "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1",
+ "body": {"id": "req1-unit1",
+ "imp": [{"id": "imp1",
+ "banner": {"w": 100, "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]}},
+ "mockResponse": {"status": 200,
+ "body": "invalid-json"}}],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [{"value": "invalid body: json: cannot unmarshal string into Go value of type openrtb.BidResponse",
+ "comparison": "literal"}]}
diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json b/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json
new file mode 100644
index 00000000000..d9cb6daa55d
--- /dev/null
+++ b/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json
@@ -0,0 +1,43 @@
+{"mockBidRequest": {"id": "req1",
+ "imp":[{"id": "imp1",
+ "video": {"minduration": 120,
+ "mimes": ["video/mp4"]},
+ "ext": {"bidder": {"adunit": "unit1"}}},
+ {"id": "imp2",
+ "video": {"minduration": 120,
+ "mimes": ["video/mp4"]},
+ "ext": {"bidder": {"adunit": "unit2"}}}]},
+ "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1",
+ "body": {"id": "req1-unit1",
+ "imp": [{"id": "imp1",
+ "video": {"minduration": 120,
+ "mimes": ["video/mp4"]},
+ "ext": {"bidder": {"adunit": "unit1"}}}]}},
+ "mockResponse": {"status": 200,
+ "body": {"id": "req1-imp1-resp1",
+ "seatbid": [{"bid": [{"id": "req1-imp1-bid1",
+ "impid": "imp1",
+ "price": 0.24,
+ "adm": "",
+ "cat": ["IAB1", "IAB2"],
+ "ext": {}}]}],
+ "cur": "USD"}}},
+ {"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2",
+ "body": {"id": "req1-unit2",
+ "imp": [{"id": "imp2",
+ "video": {"minduration": 120,
+ "mimes": ["video/mp4"]},
+ "ext": {"bidder": {"adunit": "unit2"}}}]}},
+ "mockResponse": {"status": 200,
+ "body": {"id": "req1-imp2-resp2",
+ "seatbid": [{"bid": [{"id": "req1-imp2-bid2",
+ "impid": "imp2",
+ "price": 0.24,
+ "adm": "",
+ "cat": ["IAB1", "IAB2"],
+ "ext": ""}]}],
+ "cur": "USD"}}}],
+ "expectedMakeBidsErrors": [{"value": "$.seatbid.bid.ext.ads.video required",
+ "comparison": "literal"},
+ {"value": "json: cannot unmarshal string into Go value of type struct { Ads *adoppler.adsImpExt \"json:\\\"ads\\\"\" }",
+ "comparison": "literal"}]}
diff --git a/adapters/adoppler/adopplertest/supplemental/missing-adunit.json b/adapters/adoppler/adopplertest/supplemental/missing-adunit.json
new file mode 100644
index 00000000000..82a6a95ed58
--- /dev/null
+++ b/adapters/adoppler/adopplertest/supplemental/missing-adunit.json
@@ -0,0 +1,9 @@
+{"mockBidRequest": {"id": "req1",
+ "imp":[{"id": "imp1",
+ "banner": {"w": 100,
+ "h": 200},
+ "ext": {"bidder": {}}}]},
+ "httpCalls": [],
+ "expectedBidResponses": [],
+ "expectedMakeRequestsErrors": [{"value": "$.imp.ext.adoppler.adunit required",
+ "comparison": "literal"}]}
diff --git a/adapters/adoppler/adopplertest/supplemental/server-error.json b/adapters/adoppler/adopplertest/supplemental/server-error.json
new file mode 100644
index 00000000000..df23bac07df
--- /dev/null
+++ b/adapters/adoppler/adopplertest/supplemental/server-error.json
@@ -0,0 +1,15 @@
+{"mockBidRequest": {"id": "req1",
+ "imp":[{"id": "imp1",
+ "banner": {"w": 100,
+ "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]},
+ "httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1",
+ "body": {"id": "req1-unit1",
+ "imp": [{"id": "imp1",
+ "banner": {"w": 100, "h": 200},
+ "ext": {"bidder": {"adunit": "unit1"}}}]}},
+ "mockResponse": {"status": 500,
+ "body": ""}}],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [{"value": "unexpected status: 500",
+ "comparison": "literal"}]}
diff --git a/adapters/adpone/adpone.go b/adapters/adpone/adpone.go
index b948ff5e383..9064e971fcb 100644
--- a/adapters/adpone/adpone.go
+++ b/adapters/adpone/adpone.go
@@ -3,9 +3,10 @@ package adpone
import (
"encoding/json"
"fmt"
- "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
"net/http"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+
"github.com/PubMatic-OpenWrap/openrtb"
"github.com/PubMatic-OpenWrap/prebid-server/adapters"
"github.com/PubMatic-OpenWrap/prebid-server/errortypes"
diff --git a/adapters/adtarget/adtarget.go b/adapters/adtarget/adtarget.go
new file mode 100644
index 00000000000..d3d13fd33de
--- /dev/null
+++ b/adapters/adtarget/adtarget.go
@@ -0,0 +1,189 @@
+package adtarget
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+type AdtargetAdapter struct {
+ endpoint string
+}
+
+type adtargetImpExt struct {
+ Adtarget openrtb_ext.ExtImpAdtarget `json:"adtarget"`
+}
+
+func (a *AdtargetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+
+ totalImps := len(request.Imp)
+ errors := make([]error, 0, totalImps)
+ imp2source := make(map[int][]int)
+
+ for i := 0; i < totalImps; i++ {
+
+ sourceId, err := validateImpressionAndSetExt(&request.Imp[i])
+
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ if _, ok := imp2source[sourceId]; !ok {
+ imp2source[sourceId] = make([]int, 0, totalImps-i)
+ }
+
+ imp2source[sourceId] = append(imp2source[sourceId], i)
+
+ }
+
+ totalReqs := len(imp2source)
+ if 0 == totalReqs {
+ return nil, errors
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ reqs := make([]*adapters.RequestData, 0, totalReqs)
+
+ imps := request.Imp
+ request.Imp = make([]openrtb.Imp, 0, len(imps))
+ for sourceId, impIndexes := range imp2source {
+ request.Imp = request.Imp[:0]
+
+ for i := 0; i < len(impIndexes); i++ {
+ request.Imp = append(request.Imp, imps[impIndexes[i]])
+ }
+
+ body, err := json.Marshal(request)
+ if err != nil {
+ errors = append(errors, fmt.Errorf("error while encoding bidRequest, err: %s", err))
+ return nil, errors
+ }
+
+ reqs = append(reqs, &adapters.RequestData{
+ Method: "POST",
+ Uri: a.endpoint + fmt.Sprintf("?aid=%d", sourceId),
+ Body: body,
+ Headers: headers,
+ })
+ }
+
+ return reqs, errors
+}
+
+func (a *AdtargetAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+
+ if httpRes.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+ if httpRes.StatusCode == http.StatusBadRequest {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", httpRes.StatusCode),
+ }}
+ }
+ var bidResp openrtb.BidResponse
+ if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("error while decoding response, err: %s", err),
+ }}
+ }
+
+ bidResponse := adapters.NewBidderResponse()
+ var errors []error
+
+ var impOK bool
+ for _, sb := range bidResp.SeatBid {
+ for i := 0; i < len(sb.Bid); i++ {
+
+ bid := sb.Bid[i]
+
+ impOK = false
+ mediaType := openrtb_ext.BidTypeBanner
+ for _, imp := range bidReq.Imp {
+ if imp.ID == bid.ImpID {
+
+ impOK = true
+
+ if imp.Video != nil {
+ mediaType = openrtb_ext.BidTypeVideo
+ break
+ }
+ }
+ }
+
+ if !impOK {
+ errors = append(errors, &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("ignoring bid id=%s, request doesn't contain any impression with id=%s", bid.ID, bid.ImpID),
+ })
+ continue
+ }
+
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: mediaType,
+ })
+ }
+ }
+
+ return bidResponse, errors
+}
+
+func validateImpressionAndSetExt(imp *openrtb.Imp) (int, error) {
+
+ if imp.Banner == nil && imp.Video == nil {
+ return 0, &errortypes.BadInput{
+ Message: fmt.Sprintf("ignoring imp id=%s, Adtarget supports only Video and Banner", imp.ID),
+ }
+ }
+
+ if 0 == len(imp.Ext) {
+ return 0, &errortypes.BadInput{
+ Message: fmt.Sprintf("ignoring imp id=%s, extImpBidder is empty", imp.ID),
+ }
+ }
+
+ var bidderExt adapters.ExtImpBidder
+
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return 0, &errortypes.BadInput{
+ Message: fmt.Sprintf("ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err),
+ }
+ }
+
+ impExt := openrtb_ext.ExtImpAdtarget{}
+ err := json.Unmarshal(bidderExt.Bidder, &impExt)
+ if err != nil {
+ return 0, &errortypes.BadInput{
+ Message: fmt.Sprintf("ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err),
+ }
+ }
+
+ // common extension for all impressions
+ var impExtBuffer []byte
+
+ impExtBuffer, err = json.Marshal(&adtargetImpExt{
+ Adtarget: impExt,
+ })
+
+ if impExt.BidFloor > 0 {
+ imp.BidFloor = impExt.BidFloor
+ }
+
+ imp.Ext = impExtBuffer
+
+ return impExt.SourceId, nil
+}
+
+func NewAdtargetBidder(endpoint string) *AdtargetAdapter {
+ return &AdtargetAdapter{
+ endpoint: endpoint,
+ }
+}
diff --git a/adapters/adtarget/adtarget_test.go b/adapters/adtarget/adtarget_test.go
new file mode 100644
index 00000000000..1fd67dfe7b1
--- /dev/null
+++ b/adapters/adtarget/adtarget_test.go
@@ -0,0 +1,11 @@
+package adtarget
+
+import (
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "adtargettest", NewAdtargetBidder("http://ghb.console.adtarget.com.tr/pbs/ortb"))
+}
diff --git a/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json b/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json
new file mode 100644
index 00000000000..518268d4fea
--- /dev/null
+++ b/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json
@@ -0,0 +1,88 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "w": 900,
+ "h": 250,
+ "mimes": [
+ "video/x-flv",
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "aid": 1000
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id":"test-imp-id",
+ "video": {
+ "w": 900,
+ "h": 250,
+ "mimes": [
+ "video/x-flv",
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "adtarget": {
+ "aid": 1000
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "w": 900,
+ "h": 250
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "w": 900,
+ "h": 250
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adtarget/adtargettest/exemplary/simple-banner.json b/adapters/adtarget/adtargettest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..b63739bda0f
--- /dev/null
+++ b/adapters/adtarget/adtargettest/exemplary/simple-banner.json
@@ -0,0 +1,62 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "aid": 1000,
+ "siteId": 1234,
+ "bidFloor": 20
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id":"test-imp-id",
+ "banner": {
+ "format": [
+ {"w":300,"h":250},
+ {"w":300,"h":600}
+ ]
+ },
+ "bidfloor": 20,
+ "ext": {
+ "adtarget": {
+ "aid": 1000,
+ "siteId": 1234,
+ "bidFloor": 20
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adtarget/adtargettest/exemplary/simple-video.json b/adapters/adtarget/adtargettest/exemplary/simple-video.json
new file mode 100644
index 00000000000..4dc4547d7d1
--- /dev/null
+++ b/adapters/adtarget/adtargettest/exemplary/simple-video.json
@@ -0,0 +1,55 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "w": 900,
+ "h": 250,
+ "mimes": [
+ "video/x-flv",
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "aid": 1000
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id":"test-imp-id",
+ "video": {
+ "w": 900,
+ "h": 250,
+ "mimes": [
+ "video/x-flv",
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "adtarget": {
+ "aid": 1000
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adtarget/adtargettest/params/race/banner.json b/adapters/adtarget/adtargettest/params/race/banner.json
new file mode 100644
index 00000000000..1d6658c71ab
--- /dev/null
+++ b/adapters/adtarget/adtargettest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+ "aid": 350975
+}
diff --git a/adapters/adtarget/adtargettest/params/race/video.json b/adapters/adtarget/adtargettest/params/race/video.json
new file mode 100644
index 00000000000..fe4207ef05c
--- /dev/null
+++ b/adapters/adtarget/adtargettest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+ "aid": 331133
+}
diff --git a/adapters/adtarget/adtargettest/supplemental/audio.json b/adapters/adtarget/adtargettest/supplemental/audio.json
new file mode 100644
index 00000000000..e2148e9db99
--- /dev/null
+++ b/adapters/adtarget/adtargettest/supplemental/audio.json
@@ -0,0 +1,25 @@
+{
+ "mockBidRequest": {
+ "id": "unsupported-audio-request",
+ "imp": [
+ {
+ "id": "unsupported-audio-imp",
+ "audio": {
+ "mimes": ["video/mp4"]
+ },
+ "ext": {
+ "bidder": {
+ "placementId": 1
+ }
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "ignoring imp id=unsupported-audio-imp, Adtarget supports only Video and Banner",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json b/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json
new file mode 100644
index 00000000000..a4e487466ea
--- /dev/null
+++ b/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json
@@ -0,0 +1,58 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 100,
+ "h": 400
+ },
+ "ext": {
+ "bidder": {
+ "aid": 1000
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 100,
+ "h": 400
+ },
+ "ext": {
+ "adtarget": {
+ "aid": 1000
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ]
+}
diff --git a/adapters/adtarget/adtargettest/supplemental/native.json b/adapters/adtarget/adtargettest/supplemental/native.json
new file mode 100644
index 00000000000..3d9aa6630eb
--- /dev/null
+++ b/adapters/adtarget/adtargettest/supplemental/native.json
@@ -0,0 +1,25 @@
+{
+ "mockBidRequest": {
+ "id": "unsupported-native-request",
+ "imp": [
+ {
+ "id": "unsupported-native-imp",
+ "native": {
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "placementId": 1
+ }
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "ignoring imp id=unsupported-native-imp, Adtarget supports only Video and Banner",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json b/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json
new file mode 100644
index 00000000000..1986dfaf13f
--- /dev/null
+++ b/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json
@@ -0,0 +1,26 @@
+{
+ "mockBidRequest": {
+ "id": "unsupported-native-request",
+ "imp": [
+ {
+ "id": "unsupported-native-imp",
+ "video": {
+ "w": 100,
+ "h": 200
+ },
+ "ext": {
+ "bidder": {
+ "aid": "some string instead of int"
+ }
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "ignoring imp id=unsupported-native-imp, error while decoding impExt, err: json: cannot unmarshal string into Go struct field ExtImpAdtarget.aid of type int",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json b/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json
new file mode 100644
index 00000000000..0dffdb2bebb
--- /dev/null
+++ b/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json
@@ -0,0 +1,77 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "w": 900,
+ "h": 250,
+ "mimes": [
+ "video/x-flv",
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "aid": 1000
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://ghb.console.adtarget.com.tr/pbs/ortb?aid=1000",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id":"test-imp-id",
+ "video": {
+ "w": 900,
+ "h": 250,
+ "mimes": [
+ "video/x-flv",
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "adtarget": {
+ "aid": 1000
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "SOME-WRONG-IMP-ID",
+ "price": 3.5,
+ "w": 900,
+ "h": 250
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "ignoring bid id=test-bid-id, request doesn't contain any impression with id=SOME-WRONG-IMP-ID",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adtarget/params_test.go b/adapters/adtarget/params_test.go
new file mode 100644
index 00000000000..61ed4885512
--- /dev/null
+++ b/adapters/adtarget/params_test.go
@@ -0,0 +1,60 @@
+package adtarget
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/adtarget.json
+// These also validate the format of the external API: request.imp[i].ext.adtarget
+// TestValidParams makes sure that the adtarget schema accepts all imp.ext fields which we intend to support.
+
+func TestValidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, validParam := range validParams {
+ if err := validator.Validate(openrtb_ext.BidderAdtarget, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected adtarget params: %s", validParam)
+ }
+ }
+}
+
+// TestInvalidParams makes sure that the adtarget schema rejects all the imp.ext fields we don't support.
+func TestInvalidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, invalidParam := range invalidParams {
+ if err := validator.Validate(openrtb_ext.BidderAdtarget, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"aid":123}`,
+ `{"aid":123,"placementId":1234}`,
+ `{"aid":123,"siteId":4321}`,
+ `{"aid":123,"siteId":0,"bidFloor":0}`,
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `4.2`,
+ `[]`,
+ `{}`,
+ `{"aid":"123"}`,
+ `{"aid":"0"}`,
+ `{"aid":"123","placementId":"123"}`,
+ `{"aid":123, "placementId":"123", "siteId":"321"}`,
+}
diff --git a/adapters/adtarget/usersync.go b/adapters/adtarget/usersync.go
new file mode 100644
index 00000000000..93e57b173f6
--- /dev/null
+++ b/adapters/adtarget/usersync.go
@@ -0,0 +1,12 @@
+package adtarget
+
+import (
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/usersync"
+)
+
+func NewAdtargetSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("adtarget", 0, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/adtarget/usersync_test.go b/adapters/adtarget/usersync_test.go
new file mode 100644
index 00000000000..ccaf7ee1bf9
--- /dev/null
+++ b/adapters/adtarget/usersync_test.go
@@ -0,0 +1,37 @@
+package adtarget
+
+import (
+ "fmt"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa"
+ "testing"
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAdtargetSyncer(t *testing.T) {
+ syncURL := "//sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=localhost%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D"
+ fmt.Println("adtarget sync")
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewAdtargetSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "0",
+ Consent: "123",
+ },
+ CCPA: ccpa.Policy{
+ Value: "1-YY",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "//sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr=0&gdpr_consent=123&us_privacy=1-YY&redir=localhost%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D0%26gdpr_consent%3D123%26uid%3D%7Buid%7D", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.EqualValues(t, 0, syncer.GDPRVendorID())
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go
index ab35436e351..60989aaa315 100644
--- a/adapters/adtelligent/adtelligent.go
+++ b/adapters/adtelligent/adtelligent.go
@@ -55,7 +55,6 @@ func (a *AdtelligentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *
imps := request.Imp
request.Imp = make([]openrtb.Imp, 0, len(imps))
-
for sourceId, impIds := range imp2source {
request.Imp = request.Imp[:0]
diff --git a/adapters/adtelligent/adtelligent_test.go b/adapters/adtelligent/adtelligent_test.go
index 9b42bbb10d1..ce8d24a3c21 100644
--- a/adapters/adtelligent/adtelligent_test.go
+++ b/adapters/adtelligent/adtelligent_test.go
@@ -7,5 +7,5 @@ import (
)
func TestJsonSamples(t *testing.T) {
- adapterstest.RunJSONBidderTest(t, "adtelligenttest", NewAdtelligentBidder("http://hb.adtelligent.com/auction"))
+ adapterstest.RunJSONBidderTest(t, "adtelligenttest", NewAdtelligentBidder("http://ghb.adtelligent.com/pbs/ortb"))
}
diff --git a/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json b/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json
index 67ad2fd2915..553ec61833b 100644
--- a/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json
+++ b/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json
@@ -24,7 +24,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://hb.adtelligent.com/auction?aid=1000",
+ "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json b/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json
index 6648229de95..a06477b4d18 100644
--- a/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json
+++ b/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json
@@ -30,7 +30,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://hb.adtelligent.com/auction?aid=1000",
+ "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json b/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json
index 97769651997..f108cc94b17 100644
--- a/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json
+++ b/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json
@@ -24,7 +24,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://hb.adtelligent.com/auction?aid=1000",
+ "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json b/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json
index 9dc279bcd1c..6155e9bc56b 100644
--- a/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json
+++ b/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json
@@ -25,7 +25,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://hb.adtelligent.com/auction?aid=1000",
+ "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json
index 94df34af40d..2e5aeff311f 100644
--- a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json
+++ b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json
@@ -24,7 +24,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://hb.adtelligent.com/auction?aid=1000",
+ "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/advangelists/usersync.go b/adapters/advangelists/usersync.go
index 5ba287757b8..b1539d0093d 100644
--- a/adapters/advangelists/usersync.go
+++ b/adapters/advangelists/usersync.go
@@ -8,5 +8,5 @@ import (
)
func NewAdvangelistsSyncer(temp *template.Template) usersync.Usersyncer {
- return adapters.NewSyncer("advangelists", 61, temp, adapters.SyncTypeIframe)
+ return adapters.NewSyncer("advangelists", 0, temp, adapters.SyncTypeIframe)
}
diff --git a/adapters/advangelists/usersync_test.go b/adapters/advangelists/usersync_test.go
index a68472fb4bf..5dae85b11a9 100644
--- a/adapters/advangelists/usersync_test.go
+++ b/adapters/advangelists/usersync_test.go
@@ -26,6 +26,6 @@ func TestAdvangelistsSyncer(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=advangelists&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&uid=$UID", syncInfo.URL)
assert.Equal(t, "iframe", syncInfo.Type)
- assert.EqualValues(t, 61, syncer.GDPRVendorID())
+ assert.EqualValues(t, 0, syncer.GDPRVendorID())
assert.Equal(t, false, syncInfo.SupportCORS)
}
diff --git a/adapters/aja/aja.go b/adapters/aja/aja.go
new file mode 100644
index 00000000000..55de9567ff8
--- /dev/null
+++ b/adapters/aja/aja.go
@@ -0,0 +1,132 @@
+package aja
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+ "net/http"
+)
+
+type AJAAdapter struct {
+ endpoint string
+}
+
+func (a *AJAAdapter) MakeRequests(bidReq *openrtb.BidRequest, extraInfo *adapters.ExtraRequestInfo) (adapterReqs []*adapters.RequestData, errs []error) {
+ // split imps by tagid
+ tagIDs := []string{}
+ impsByTagID := map[string][]openrtb.Imp{}
+ for _, imp := range bidReq.Imp {
+ extAJA, err := parseExtAJA(imp)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ imp.TagID = extAJA.AdSpotID
+ imp.Ext = nil
+ if _, ok := impsByTagID[imp.TagID]; !ok {
+ tagIDs = append(tagIDs, imp.TagID)
+ }
+ impsByTagID[imp.TagID] = append(impsByTagID[imp.TagID], imp)
+ }
+
+ req := *bidReq
+ for _, tagID := range tagIDs {
+ req.Imp = impsByTagID[tagID]
+ body, err := json.Marshal(req)
+ if err != nil {
+ errs = append(errs, &errortypes.BadInput{
+ Message: fmt.Sprintf("Failed to unmarshal bidrequest ID: %s err: %s", bidReq.ID, err),
+ })
+ continue
+ }
+ adapterReqs = append(adapterReqs, &adapters.RequestData{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: body,
+ })
+ }
+
+ return
+}
+
+func parseExtAJA(imp openrtb.Imp) (openrtb_ext.ExtImpAJA, error) {
+ var (
+ extImp adapters.ExtImpBidder
+ extAJA openrtb_ext.ExtImpAJA
+ )
+
+ if err := json.Unmarshal(imp.Ext, &extImp); err != nil {
+ return extAJA, &errortypes.BadInput{
+ Message: fmt.Sprintf("Failed to unmarshal ext impID: %s err: %s", imp.ID, err),
+ }
+ }
+
+ if err := json.Unmarshal(extImp.Bidder, &extAJA); err != nil {
+ return extAJA, &errortypes.BadInput{
+ Message: fmt.Sprintf("Failed to unmarshal ext.bidder impID: %s err: %s", imp.ID, err),
+ }
+ }
+
+ return extAJA, nil
+}
+
+func (a *AJAAdapter) MakeBids(bidReq *openrtb.BidRequest, adapterReq *adapters.RequestData, adapterResp *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if adapterResp.StatusCode != http.StatusOK {
+ if adapterResp.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+ if adapterResp.StatusCode == http.StatusBadRequest {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("Unexpected status code: %d", adapterResp.StatusCode),
+ }}
+ }
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unexpected status code: %d", adapterResp.StatusCode),
+ }}
+ }
+
+ var bidResp openrtb.BidResponse
+ if err := json.Unmarshal(adapterResp.Body, &bidResp); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Failed to unmarshal bid response: %s", err.Error()),
+ }}
+ }
+
+ bidderResp := adapters.NewBidderResponseWithBidsCapacity(len(bidReq.Imp))
+ var errors []error
+
+ for _, seatbid := range bidResp.SeatBid {
+ for _, bid := range seatbid.Bid {
+ for _, imp := range bidReq.Imp {
+ if imp.ID == bid.ImpID {
+ var bidType openrtb_ext.BidType
+ if imp.Banner != nil {
+ bidType = openrtb_ext.BidTypeBanner
+ } else if imp.Video != nil {
+ bidType = openrtb_ext.BidTypeVideo
+ } else {
+ errors = append(errors, &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Response received for unexpected type of bid bidID: %s", bid.ID),
+ })
+ continue
+ }
+ bidderResp.Bids = append(bidderResp.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: bidType,
+ })
+ break
+ }
+ }
+ }
+ }
+ return bidderResp, errors
+}
+
+func NewAJABidder(endpoint string) adapters.Bidder {
+ return &AJAAdapter{
+ endpoint: endpoint,
+ }
+}
diff --git a/adapters/aja/aja_test.go b/adapters/aja/aja_test.go
new file mode 100644
index 00000000000..95906b14c2a
--- /dev/null
+++ b/adapters/aja/aja_test.go
@@ -0,0 +1,13 @@
+package aja
+
+import (
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+)
+
+const testsBidderEndpoint = "https://localhost/bid/4"
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "ajatest", NewAJABidder(testsBidderEndpoint))
+}
diff --git a/adapters/aja/ajatest/exemplary/banner-multiple-imps.json b/adapters/aja/ajatest/exemplary/banner-multiple-imps.json
new file mode 100644
index 00000000000..8de9a31eadb
--- /dev/null
+++ b/adapters/aja/ajatest/exemplary/banner-multiple-imps.json
@@ -0,0 +1,159 @@
+{
+ "mockBidRequest": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "asi": "test-asi"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id2",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "asi": "test-asi2"
+ }
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ },
+
+ "httpcalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://localhost/bid/4",
+ "headers": {},
+ "body": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "tagid": "test-asi"
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-req-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 1,
+ "adm": "test
",
+ "crid": "test-creative-id"
+ }
+ ]
+ }
+ ],
+ "bidid": "test-seatbid-id",
+ "cur": "USD"
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "uri": "https://localhost/bid/4",
+ "headers": {},
+ "body": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id2",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "tagid": "test-asi2"
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-req-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-bid-id2",
+ "impid": "test-imp-id2",
+ "price": 1,
+ "adm": "test2
",
+ "crid": "test-creative-id2"
+ }
+ ]
+ }
+ ],
+ "bidid": "test-seatbid-id",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 1,
+ "adm": "test
",
+ "crid": "test-creative-id"
+ },
+ "type": "banner"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id2",
+ "impid": "test-imp-id2",
+ "price": 1,
+ "adm": "test2
",
+ "crid": "test-creative-id2"
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/aja/ajatest/exemplary/video.json b/adapters/aja/ajatest/exemplary/video.json
new file mode 100644
index 00000000000..a7991570bba
--- /dev/null
+++ b/adapters/aja/ajatest/exemplary/video.json
@@ -0,0 +1,90 @@
+{
+ "mockBidRequest": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "asi": "test-asi"
+ }
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://localhost/bid/4",
+ "headers": {},
+ "body": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "tagid": "test-asi"
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-req-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 1,
+ "adm": "",
+ "crid": "test-creative-id"
+ }
+ ]
+ }
+ ],
+ "bidid": "test-seatbid-id",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 1,
+ "adm": "",
+ "crid": "test-creative-id"
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aja/ajatest/params/race/banner.json b/adapters/aja/ajatest/params/race/banner.json
new file mode 100644
index 00000000000..6d50c2d1880
--- /dev/null
+++ b/adapters/aja/ajatest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+ "asi": "abc123"
+}
\ No newline at end of file
diff --git a/adapters/aja/ajatest/params/race/video.json b/adapters/aja/ajatest/params/race/video.json
new file mode 100644
index 00000000000..6d50c2d1880
--- /dev/null
+++ b/adapters/aja/ajatest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+ "asi": "abc123"
+}
\ No newline at end of file
diff --git a/adapters/aja/ajatest/supplemental/invalid-bid-type.json b/adapters/aja/ajatest/supplemental/invalid-bid-type.json
new file mode 100644
index 00000000000..1bba635f731
--- /dev/null
+++ b/adapters/aja/ajatest/supplemental/invalid-bid-type.json
@@ -0,0 +1,71 @@
+{
+ "mockBidRequest": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "ext": {
+ "bidder": {
+ "asi": "test-asi"
+ }
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://localhost/bid/4",
+ "headers": {},
+ "body": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "test-asi"
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-req-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 1,
+ "adm": "",
+ "crid": "test-creative-id"
+ }
+ ]
+ }
+ ],
+ "bidid": "test-seatbid-id",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Response received for unexpected type of bid bidID: test-bid-id",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aja/ajatest/supplemental/invalid-ext-bidder.json b/adapters/aja/ajatest/supplemental/invalid-ext-bidder.json
new file mode 100644
index 00000000000..b12b431b0ed
--- /dev/null
+++ b/adapters/aja/ajatest/supplemental/invalid-ext-bidder.json
@@ -0,0 +1,36 @@
+{
+ "mockBidRequest": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "asi": 111
+ }
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ },
+
+ "httpCalls": [],
+
+ "expectedBidResponses": [],
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Failed to unmarshal ext.bidder impID: test-imp-id err: json: cannot unmarshal number into Go struct field ExtImpAJA.asi of type string",
+ "comparison": "literal"
+ }
+
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aja/ajatest/supplemental/invalid-ext.json b/adapters/aja/ajatest/supplemental/invalid-ext.json
new file mode 100644
index 00000000000..478222d0ee9
--- /dev/null
+++ b/adapters/aja/ajatest/supplemental/invalid-ext.json
@@ -0,0 +1,32 @@
+{
+ "mockBidRequest": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": 111
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ },
+
+ "httpCalls": [],
+
+ "expectedBidResponses": [],
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Failed to unmarshal ext impID: test-imp-id err: json: cannot unmarshal number into Go value of type adapters.ExtImpBidder",
+ "comparison": "literal"
+ }
+
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aja/ajatest/supplemental/status-bad-request.json b/adapters/aja/ajatest/supplemental/status-bad-request.json
new file mode 100644
index 00000000000..a47db8bbca9
--- /dev/null
+++ b/adapters/aja/ajatest/supplemental/status-bad-request.json
@@ -0,0 +1,64 @@
+{
+ "mockBidRequest": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "asi": "test-asi"
+ }
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://localhost/bid/4",
+ "headers": {},
+ "body": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "tagid": "test-asi"
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ }
+ },
+ "mockResponse": {
+ "status": 400,
+ "body": {}
+ }
+ }
+ ],
+
+ "expectedBidResponses": [],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aja/ajatest/supplemental/status-internal-server-error.json b/adapters/aja/ajatest/supplemental/status-internal-server-error.json
new file mode 100644
index 00000000000..5d36dc5dcdc
--- /dev/null
+++ b/adapters/aja/ajatest/supplemental/status-internal-server-error.json
@@ -0,0 +1,64 @@
+{
+ "mockBidRequest": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "asi": "test-asi"
+ }
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://localhost/bid/4",
+ "headers": {},
+ "body": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "tagid": "test-asi"
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ }
+ },
+ "mockResponse": {
+ "status": 500,
+ "body": {}
+ }
+ }
+ ],
+
+ "expectedBidResponses": [],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 500",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aja/ajatest/supplemental/status-no-content.json b/adapters/aja/ajatest/supplemental/status-no-content.json
new file mode 100644
index 00000000000..e12fd21a26a
--- /dev/null
+++ b/adapters/aja/ajatest/supplemental/status-no-content.json
@@ -0,0 +1,57 @@
+{
+ "mockBidRequest": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "asi": "test-asi"
+ }
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://localhost/bid/4",
+ "headers": {},
+ "body": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "tagid": "test-asi"
+ }
+ ],
+ "user": {
+ "buyeruid": "test-uid"
+ },
+ "tmax": 500
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/aja/usersync.go b/adapters/aja/usersync.go
new file mode 100644
index 00000000000..deddbabb1d9
--- /dev/null
+++ b/adapters/aja/usersync.go
@@ -0,0 +1,12 @@
+package aja
+
+import (
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/usersync"
+)
+
+func NewAJASyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("aja", 0, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/aja/usersync_test.go b/adapters/aja/usersync_test.go
new file mode 100644
index 00000000000..54b3ed01212
--- /dev/null
+++ b/adapters/aja/usersync_test.go
@@ -0,0 +1,35 @@
+package aja
+
+import (
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa"
+ "testing"
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAJASyncer(t *testing.T) {
+ syncURL := "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir=localhost/setuid?bidder=aja&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&uid=%s"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewAJASyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "1",
+ Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA",
+ },
+ CCPA: ccpa.Policy{
+ Value: "C",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr=1&us_privacy=C&redir=localhost/setuid?bidder=aja&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&uid=%s", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.EqualValues(t, 0, syncer.GDPRVendorID())
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go
index a768133bdaf..1b3b42295d7 100644
--- a/adapters/appnexus/appnexus.go
+++ b/adapters/appnexus/appnexus.go
@@ -87,6 +87,7 @@ type appnexusBidExtAppnexus struct {
BrandId int `json:"brand_id"`
BrandCategory int `json:"brand_category_id"`
CreativeInfo appnexusBidExtCreative `json:"creative_info"`
+ DealPriority int `json:"deal_priority"`
}
type appnexusBidExt struct {
@@ -543,9 +544,10 @@ func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb.BidRequest, external
}
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
- Bid: &bid,
- BidType: bidType,
- BidVideo: impVideo,
+ Bid: &bid,
+ BidType: bidType,
+ BidVideo: impVideo,
+ DealPriority: bidExt.Appnexus.DealPriority,
})
} else {
errs = append(errs, err)
diff --git a/adapters/appnexus/appnexusplatformtest/exemplary/simple-auction.json b/adapters/appnexus/appnexusplatformtest/exemplary/simple-auction.json
index 03c3f4c5880..e0c0435faab 100644
--- a/adapters/appnexus/appnexusplatformtest/exemplary/simple-auction.json
+++ b/adapters/appnexus/appnexusplatformtest/exemplary/simple-auction.json
@@ -80,7 +80,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
}]
@@ -118,7 +119,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/appnexus/appnexusplatformtest/video/simple-video.json b/adapters/appnexus/appnexusplatformtest/video/simple-video.json
index 85960427d81..7ee192be2c1 100644
--- a/adapters/appnexus/appnexusplatformtest/video/simple-video.json
+++ b/adapters/appnexus/appnexusplatformtest/video/simple-video.json
@@ -80,7 +80,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
}]
@@ -118,7 +119,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/appnexus/appnexustest/amp/simple-banner.json b/adapters/appnexus/appnexustest/amp/simple-banner.json
index 646359b4267..54e6a143e19 100644
--- a/adapters/appnexus/appnexustest/amp/simple-banner.json
+++ b/adapters/appnexus/appnexustest/amp/simple-banner.json
@@ -91,7 +91,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
}]
@@ -129,7 +130,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/appnexus/appnexustest/amp/simple-video.json b/adapters/appnexus/appnexustest/amp/simple-video.json
index a6f96be34b8..061d5c94369 100644
--- a/adapters/appnexus/appnexustest/amp/simple-video.json
+++ b/adapters/appnexus/appnexustest/amp/simple-video.json
@@ -82,7 +82,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
}]
@@ -120,7 +121,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/appnexus/appnexustest/exemplary/native-1.1.json b/adapters/appnexus/appnexustest/exemplary/native-1.1.json
index 86b75505e0c..189304fdb4c 100644
--- a/adapters/appnexus/appnexustest/exemplary/native-1.1.json
+++ b/adapters/appnexus/appnexustest/exemplary/native-1.1.json
@@ -96,7 +96,8 @@
"brand_category_id": 350,
"auction_id": 5607483846416358664,
"bidder_id": 2,
- "bid_ad_type": 3
+ "bid_ad_type": 3,
+ "deal_priority": 5
}
}
}
@@ -136,7 +137,8 @@
"brand_category_id": 350,
"auction_id": 5607483846416358664,
"bidder_id": 2,
- "bid_ad_type": 3
+ "bid_ad_type": 3,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/appnexus/appnexustest/exemplary/simple-banner.json b/adapters/appnexus/appnexustest/exemplary/simple-banner.json
index e5bd311648f..59931fb6ad7 100644
--- a/adapters/appnexus/appnexustest/exemplary/simple-banner.json
+++ b/adapters/appnexus/appnexustest/exemplary/simple-banner.json
@@ -89,7 +89,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
}]
@@ -127,7 +128,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/appnexus/appnexustest/exemplary/simple-video.json b/adapters/appnexus/appnexustest/exemplary/simple-video.json
index 15755c7de37..ced90c39549 100644
--- a/adapters/appnexus/appnexustest/exemplary/simple-video.json
+++ b/adapters/appnexus/appnexustest/exemplary/simple-video.json
@@ -80,7 +80,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
}]
@@ -118,7 +119,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/appnexus/appnexustest/exemplary/video-invalid-category.json b/adapters/appnexus/appnexustest/exemplary/video-invalid-category.json
index d3686af00a9..257905c873f 100644
--- a/adapters/appnexus/appnexustest/exemplary/video-invalid-category.json
+++ b/adapters/appnexus/appnexustest/exemplary/video-invalid-category.json
@@ -79,7 +79,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
}]
@@ -116,7 +117,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 1,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/appnexus/appnexustest/supplemental/displaymanager-test.json b/adapters/appnexus/appnexustest/supplemental/displaymanager-test.json
index d5c981c6945..c6ad330e3a8 100644
--- a/adapters/appnexus/appnexustest/supplemental/displaymanager-test.json
+++ b/adapters/appnexus/appnexustest/supplemental/displaymanager-test.json
@@ -106,7 +106,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
}]
@@ -144,7 +145,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/appnexus/appnexustest/supplemental/multi-bid.json b/adapters/appnexus/appnexustest/supplemental/multi-bid.json
index 7234551ea3f..9e63bdced95 100644
--- a/adapters/appnexus/appnexustest/supplemental/multi-bid.json
+++ b/adapters/appnexus/appnexustest/supplemental/multi-bid.json
@@ -89,7 +89,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 4
}
}
},
@@ -112,7 +113,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
}]
@@ -150,7 +152,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 4
}
}
},
@@ -177,7 +180,8 @@
"auction_id": 8189378542222915032,
"bid_ad_type": 0,
"bidder_id": 2,
- "ranking_price": 0.000000
+ "ranking_price": 0.000000,
+ "deal_priority": 5
}
}
},
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json
index 632629b53a2..f5f92515e26 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json
@@ -51,7 +51,7 @@
]
},
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"imp": [
{
"id": "test-imp-id",
@@ -84,7 +84,7 @@
},
"tmax": 500,
"ext": {
- "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e",
+ "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174",
"platformid": "test-platform-id"
}
}
@@ -92,7 +92,7 @@
"mockResponse": {
"status": 200,
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"seatbid": [
{
"bid": [
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json
index 630e26d3f90..bad228d5f18 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json
@@ -52,7 +52,7 @@
]
},
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"imp": [
{
"id": "test-imp-id",
@@ -86,7 +86,7 @@
},
"tmax": 500,
"ext": {
- "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e",
+ "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174",
"platformid": "test-platform-id"
}
}
@@ -94,7 +94,7 @@
"mockResponse": {
"status": 200,
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"seatbid": [
{
"bid": [
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json
index 288c7c14e5d..9090d80d099 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json
@@ -45,7 +45,7 @@
]
},
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"imp": [
{
"id": "test-imp-id",
@@ -78,7 +78,7 @@
},
"tmax": 500,
"ext": {
- "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e",
+ "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174",
"platformid": "test-platform-id"
}
}
@@ -86,7 +86,7 @@
"mockResponse": {
"status": 200,
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"seatbid": [
{
"bid": [
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json
index 15563c2ada5..22c62f8b821 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json
@@ -50,7 +50,7 @@
]
},
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"imp": [
{
"id": "test-imp-id",
@@ -88,7 +88,7 @@
},
"tmax": 500,
"ext": {
- "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e",
+ "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174",
"platformid": "test-platform-id"
}
}
@@ -96,7 +96,7 @@
"mockResponse": {
"status": 200,
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"seatbid": [
{
"bid": [
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json
index 52b7655593a..3edd6569258 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json
@@ -53,7 +53,7 @@
]
},
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"imp": [
{
"id": "test-imp-id",
@@ -86,7 +86,7 @@
},
"tmax": 500,
"ext": {
- "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e",
+ "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174",
"platformid": "test-platform-id"
}
}
@@ -94,7 +94,7 @@
"mockResponse": {
"status": 200,
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"seatbid": [
{
"bid": [
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json
index 0fe836af4de..16e8aede10c 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json
@@ -70,7 +70,7 @@
]
},
"body": {
- "id": "test-req-id",
+ "id": "test-imp-1",
"imp": [
{
"id": "test-imp-1",
@@ -103,7 +103,7 @@
},
"tmax": 500,
"ext": {
- "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e",
+ "authentication_id": "dfecd103a45daeb2a01728afb8ce78f6738f6007ecfebe1ca616b196e22b43e9",
"platformid": "test-platform-id"
}
}
@@ -111,7 +111,7 @@
"mockResponse": {
"status": 200,
"body": {
- "id": "test-req-id",
+ "id": "test-imp-1",
"seatbid": [
{
"bid": [
@@ -147,7 +147,7 @@
]
},
"body": {
- "id": "test-req-id",
+ "id": "test-imp-2",
"imp": [
{
"id": "test-imp-2",
@@ -180,7 +180,7 @@
},
"tmax": 500,
"ext": {
- "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e",
+ "authentication_id": "a5fead11a4db86d0f62f57c3d8001640227120c8ef236549f0db010c1dbab399",
"platformid": "test-platform-id"
}
}
@@ -188,7 +188,7 @@
"mockResponse": {
"status": 200,
"body": {
- "id": "test-req-id",
+ "id": "test-imp-2",
"seatbid": [
{
"bid": [
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json
new file mode 100644
index 00000000000..bb192aad76f
--- /dev/null
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json
@@ -0,0 +1,91 @@
+{
+ "mockBidRequest": {
+ "id": "test-req-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "publisherid": "123",
+ "placementid": "456"
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "prebid.org",
+ "page": "prebid.org"
+ },
+ "device": {
+ "ip": "152.193.6.74"
+ },
+ "user": {
+ "id": "db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid": "v4_bidder_token"
+ },
+ "tmax": 500
+ },
+ "httpcalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://an.facebook.com/placementbid.ortb",
+ "headers": {
+ "Accept": [
+ "application/json"
+ ],
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "X-Fb-Pool-Routing-Token": [
+ "v4_bidder_token"
+ ]
+ },
+ "body": {
+ "id": "test-imp-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "w": -1,
+ "h": -1
+ },
+ "tagid": "123_456"
+ }
+ ],
+ "ext": {
+ "appnexus": {
+ "hb_source": 5
+ },
+ "prebid": {}
+ },
+ "site": {
+ "domain": "prebid.org",
+ "page": "prebid.org",
+ "publisher": {
+ "id": "123"
+ }
+ },
+ "device": {
+ "ip": "152.193.6.74"
+ },
+ "user": {
+ "id": "db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid": "v4_bidder_token"
+ },
+ "tmax": 500,
+ "ext": {
+ "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174",
+ "platformid": "test-platform-id"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ]
+}
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json
index b99834ab1df..4c561c55276 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json
@@ -39,7 +39,7 @@
"expectedRequest": {
"uri": "https://an.facebook.com/placementbid.ortb",
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"imp": [
{
"id": "test-imp-id",
@@ -72,7 +72,7 @@
},
"tmax": 500,
"ext": {
- "authentication_id": "b2f9edfd707106adb6b692520081ad7e2a345444af1a895310228297a1b6247e",
+ "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174",
"platformid": "test-platform-id"
}
}
@@ -80,7 +80,7 @@
"mockResponse": {
"status": 200,
"body": {
- "id": "test-req-id",
+ "id": "test-imp-id",
"seatbid": [
{
"bid": [
diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go
index 19cc0290f15..3bc072a8385 100644
--- a/adapters/audienceNetwork/facebook.go
+++ b/adapters/audienceNetwork/facebook.go
@@ -130,6 +130,11 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error {
return err
}
+ // Every outgoing FAN request has a single impression, so we can safely use the unique
+ // impression ID as the FAN request ID. We need to make sure that we update the request
+ // ID *BEFORE* we generate the auth ID since its a hash based on the request ID
+ out.ID = imp.ID
+
reqExt := facebookReqExt{
PlatformID: this.platformID,
AuthID: this.makeAuthID(out),
@@ -333,6 +338,12 @@ func modifyImpCustom(json []byte, imp *openrtb.Imp) ([]byte, error) {
}
func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ /* No bid response */
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ /* Any other http status codes outside of 200 and 204 should be treated as errors */
if response.StatusCode != http.StatusOK {
msg := response.Headers.Get("x-fb-an-errors")
return nil, []error{&errortypes.BadInput{
@@ -447,3 +458,41 @@ func NewFacebookBidder(client *http.Client, platformID string, appSecret string)
appSecret: appSecret,
}
}
+
+func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) {
+ var (
+ rID string
+ pubID string
+ err error
+ )
+
+ // Note, the facebook adserver can only handle single impression requests, so we have to split multi-imp requests into
+ // multiple request. In order to ensure that every split request has a unique ID, the split request IDs are set to the
+ // corresponding imp's ID
+ rID, err = jsonparser.GetString(req.Body, "id")
+ if err != nil {
+ return &adapters.RequestData{}, []error{err}
+ }
+
+ // The publisher ID is either in the app object or the site object, depending on the supply of the request so we need
+ // to check both
+ pubID, err = jsonparser.GetString(req.Body, "app", "publisher", "id")
+ if err != nil {
+ pubID, err = jsonparser.GetString(req.Body, "site", "publisher", "id")
+ if err != nil {
+ return &adapters.RequestData{}, []error{
+ errors.New("path [app|site].publisher.id not found in the request"),
+ }
+ }
+ }
+
+ uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", fa.platformID, pubID, rID)
+ timeoutReq := adapters.RequestData{
+ Method: "GET",
+ Uri: uri,
+ Body: nil,
+ Headers: http.Header{},
+ }
+
+ return &timeoutReq, nil
+}
diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go
index 9c89ee74079..b4744dce211 100644
--- a/adapters/audienceNetwork/facebook_test.go
+++ b/adapters/audienceNetwork/facebook_test.go
@@ -4,7 +4,9 @@ import (
"testing"
"time"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
"github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+ "github.com/stretchr/testify/assert"
)
type tagInfo struct {
@@ -40,3 +42,54 @@ type FacebookExt struct {
func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "audienceNetworktest", NewFacebookBidder(nil, "test-platform-id", "test-app-secret"))
}
+
+func TestMakeTimeoutNoticeApp(t *testing.T) {
+ req := adapters.RequestData{
+ Body: []byte(`{"id":"1234","imp":[{"id":"1234"}],"app":{"publisher":{"id":"5678"}}}`),
+ }
+ fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret")
+
+ tb, ok := fba.(adapters.TimeoutBidder)
+ if !ok {
+ t.Error("Facebook adapter is not a TimeoutAdapter")
+ }
+
+ toReq, err := tb.MakeTimeoutNotification(&req)
+ assert.Nil(t, err, "Facebook MakeTimeoutNotification() return an error %v", err)
+ expectedUri := "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=5678&auction=1234&ortb_loss_code=2"
+ assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.")
+}
+
+func TestMakeTimeoutNoticeSite(t *testing.T) {
+ req := adapters.RequestData{
+ Body: []byte(`{"id":"1234","imp":[{"id":"1234"}],"site":{"publisher":{"id":"5678"}}}`),
+ }
+ fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret")
+
+ tb, ok := fba.(adapters.TimeoutBidder)
+ if !ok {
+ t.Error("Facebook adapter is not a TimeoutAdapter")
+ }
+
+ toReq, err := tb.MakeTimeoutNotification(&req)
+ assert.Nil(t, err, "Facebook MakeTimeoutNotification() return an error %v", err)
+ expectedUri := "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=5678&auction=1234&ortb_loss_code=2"
+ assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.")
+}
+
+func TestMakeTimeoutNoticeBadRequest(t *testing.T) {
+ req := adapters.RequestData{
+ Body: []byte(`{"imp":[{{"id":"1234"}}`),
+ }
+ fba := NewFacebookBidder(nil, "test-platform-id", "test-app-secret")
+
+ tb, ok := fba.(adapters.TimeoutBidder)
+ if !ok {
+ t.Error("Facebook adapter is not a TimeoutAdapter")
+ }
+
+ toReq, err := tb.MakeTimeoutNotification(&req)
+ assert.Empty(t, toReq.Uri, "Facebook MakeTimeoutNotification() did not return nil", err)
+ assert.NotNil(t, err, "Facebook MakeTimeoutNotification() did not return an error")
+
+}
diff --git a/adapters/avocet/avocet.go b/adapters/avocet/avocet.go
new file mode 100644
index 00000000000..ef6e1bb4344
--- /dev/null
+++ b/adapters/avocet/avocet.go
@@ -0,0 +1,124 @@
+package avocet
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+// AvocetAdapter implements a adapters.Bidder compatible with the Avocet advertising platform.
+type AvocetAdapter struct {
+ // Endpoint is a http endpoint to use when making requests to the Avocet advertising platform.
+ Endpoint string
+}
+
+func (a *AvocetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ if len(request.Imp) == 0 {
+ return nil, nil
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+ body, err := json.Marshal(request)
+ if err != nil {
+ return nil, []error{&errortypes.FailedToRequestBids{
+ Message: err.Error(),
+ }}
+ }
+ reqData := &adapters.RequestData{
+ Method: http.MethodPost,
+ Uri: a.Endpoint,
+ Body: body,
+ Headers: headers,
+ }
+ return []*adapters.RequestData{reqData}, nil
+}
+
+type avocetBidExt struct {
+ Avocet avocetBidExtension `json:"avocet"`
+}
+
+type avocetBidExtension struct {
+ Duration int `json:"duration"`
+ DealPriority int `json:"deal_priority"`
+}
+
+func (a *AvocetAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if response.StatusCode != http.StatusOK {
+ var errStr string
+ if len(response.Body) > 0 {
+ errStr = string(response.Body)
+ } else {
+ errStr = "no response body"
+ }
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("received status code: %v error: %s", response.StatusCode, errStr),
+ }}
+ }
+
+ var br openrtb.BidResponse
+ err := json.Unmarshal(response.Body, &br)
+ if err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: err.Error(),
+ }}
+ }
+ var errs []error
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+ for i := range br.SeatBid {
+ for j := range br.SeatBid[i].Bid {
+ var ext avocetBidExt
+ if len(br.SeatBid[i].Bid[j].Ext) > 0 {
+ err := json.Unmarshal(br.SeatBid[i].Bid[j].Ext, &ext)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ }
+ tbid := &adapters.TypedBid{
+ Bid: &br.SeatBid[i].Bid[j],
+ DealPriority: ext.Avocet.DealPriority,
+ }
+ tbid.BidType = getBidType(br.SeatBid[i].Bid[j], ext)
+ if tbid.BidType == openrtb_ext.BidTypeVideo {
+ tbid.BidVideo = &openrtb_ext.ExtBidPrebidVideo{
+ Duration: ext.Avocet.Duration,
+ }
+ }
+ bidResponse.Bids = append(bidResponse.Bids, tbid)
+ }
+ }
+ return bidResponse, nil
+}
+
+// getBidType returns the openrtb_ext.BidType for the provided bid.
+func getBidType(bid openrtb.Bid, ext avocetBidExt) openrtb_ext.BidType {
+ if ext.Avocet.Duration != 0 {
+ return openrtb_ext.BidTypeVideo
+ }
+ switch bid.API {
+ case openrtb.APIFrameworkVPAID10, openrtb.APIFrameworkVPAID20:
+ return openrtb_ext.BidTypeVideo
+ default:
+ return openrtb_ext.BidTypeBanner
+ }
+}
+
+// NewAvocetAdapter returns a new AvocetAdapter using the provided endpoint.
+func NewAvocetAdapter(endpoint string) *AvocetAdapter {
+ return &AvocetAdapter{
+ Endpoint: endpoint,
+ }
+}
diff --git a/adapters/avocet/avocet/exemplary/banner.json b/adapters/avocet/avocet/exemplary/banner.json
new file mode 100644
index 00000000000..b5e308ea725
--- /dev/null
+++ b/adapters/avocet/avocet/exemplary/banner.json
@@ -0,0 +1,106 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "placement": "5ea9601ac865f911007f1b6a"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "placement": "5ea9601ac865f911007f1b6a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "bidid": "dd87f80c-16a0-43c8-a673-b94b3ea4d417",
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "adm": "",
+ "adomain": ["avocet.io"],
+ "cid": "5b51e2d689654741306813a4",
+ "crid": "5b51e49634f2021f127ff7c9",
+ "h": 250,
+ "id": "bc708396-9202-437b-b726-08b9864cb8b8",
+ "impid": "test-imp-id",
+ "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg",
+ "language": "en",
+ "price": 15.64434783,
+ "w": 300
+ }
+ ],
+ "seat": "TEST_SEAT_ID"
+ }
+ ]
+ }
+ }
+ }
+ ],
+
+ "expectedBids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": ["avocet.io"],
+ "cid": "5b51e2d689654741306813a4",
+ "crid": "5b51e49634f2021f127ff7c9",
+ "h": 250,
+ "id": "bc708396-9202-437b-b726-08b9864cb8b8",
+ "impid": "test-imp-id",
+ "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg",
+ "language": "en",
+ "price": 15.64434783,
+ "w": 300
+ },
+ "type": "banner"
+ }
+ ]
+}
diff --git a/adapters/avocet/avocet/exemplary/video.json b/adapters/avocet/avocet/exemplary/video.json
new file mode 100644
index 00000000000..2398256b0dd
--- /dev/null
+++ b/adapters/avocet/avocet/exemplary/video.json
@@ -0,0 +1,104 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 1920,
+ "h": 1080
+ },
+ "ext": {
+ "bidder": {
+ "placement": "5ea9601ac865f911007f1b6a"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 1920,
+ "h": 1080
+ },
+ "ext": {
+ "bidder": {
+ "placement": "5ea9601ac865f911007f1b6a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "bidid": "a0eec3aa-f9f6-42fb-9aa4-f1b5656d4f42",
+ "id": "749d36d7-c993-455f-aefd-ffd8a7e3ccf",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "adm": "Avocet",
+ "adomain": ["avocet.io"],
+ "cid": "5b51e2d689654741306813a4",
+ "crid": "5ec530e32d57fe1100f17d87",
+ "h": 396,
+ "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f",
+ "impid": "dfp-ad--top-above-nav",
+ "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg",
+ "language": "en",
+ "price": 15.64434783,
+ "w": 600,
+ "ext": {
+ "avocet": {
+ "duration": 30
+ }
+ }
+ }
+ ],
+ "seat": "TEST_SEAT_ID"
+ }
+ ]
+ }
+ }
+ }
+ ],
+
+ "expectedBids": [
+ {
+ "bid": {
+ "adm": "Avocet",
+ "adomain": ["avocet.io"],
+ "cid": "5b51e2d689654741306813a4",
+ "crid": "5ec530e32d57fe1100f17d87",
+ "h": 396,
+ "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f",
+ "impid": "dfp-ad--top-above-nav",
+ "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg",
+ "language": "en",
+ "price": 15.64434783,
+ "w": 600,
+ "ext": {
+ "avocet": {
+ "duration": 30
+ }
+ }
+ },
+ "type": "video"
+ }
+ ]
+}
diff --git a/adapters/avocet/avocet_test.go b/adapters/avocet/avocet_test.go
new file mode 100644
index 00000000000..9c8d3d07932
--- /dev/null
+++ b/adapters/avocet/avocet_test.go
@@ -0,0 +1,301 @@
+package avocet
+
+import (
+ "encoding/json"
+ "net/http"
+ "reflect"
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "avocet", NewAvocetAdapter("https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013"))
+}
+
+func TestAvocetAdapter_MakeRequests(t *testing.T) {
+ type fields struct {
+ Endpoint string
+ }
+ type args struct {
+ request *openrtb.BidRequest
+ reqInfo *adapters.ExtraRequestInfo
+ }
+ type reqData []*adapters.RequestData
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want []*adapters.RequestData
+ wantErrs []error
+ }{
+ {
+ name: "return nil if zero imps",
+ fields: fields{Endpoint: "https://bid.avct.cloud"},
+ args: args{
+ &openrtb.BidRequest{},
+ nil,
+ },
+ want: nil,
+ wantErrs: nil,
+ },
+ {
+ name: "makes POST request with JSON content",
+ fields: fields{Endpoint: "https://bid.avct.cloud"},
+ args: args{
+ &openrtb.BidRequest{Imp: []openrtb.Imp{{}}},
+ nil,
+ },
+ want: reqData{
+ &adapters.RequestData{
+ Method: http.MethodPost,
+ Uri: "https://bid.avct.cloud",
+ Body: []byte(`{"id":"","imp":[{"id":""}]}`),
+ Headers: map[string][]string{
+ "Accept": {"application/json"},
+ "Content-Type": {"application/json;charset=utf-8"},
+ },
+ },
+ },
+ wantErrs: nil,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := &AvocetAdapter{
+ Endpoint: tt.fields.Endpoint,
+ }
+ got, got1 := a.MakeRequests(tt.args.request, tt.args.reqInfo)
+ if len(got) != len(tt.want) {
+ t.Errorf("AvocetAdapter.MakeRequests() got %v requests, wanted %v requests", len(got), len(tt.want))
+ }
+ if len(got) == len(tt.want) {
+ for i := range tt.want {
+ if !reflect.DeepEqual(got[i], tt.want[i]) {
+ t.Errorf("AvocetAdapter.MakeRequests() got = %v, want %v", got[i], tt.want[i])
+ }
+ }
+ }
+ if !reflect.DeepEqual(got1, tt.wantErrs) {
+ t.Errorf("AvocetAdapter.MakeRequests() got1 = %v, want %v", got1, tt.wantErrs)
+ }
+ })
+ }
+}
+
+func TestAvocetAdapter_MakeBids(t *testing.T) {
+ type fields struct {
+ Endpoint string
+ }
+ type args struct {
+ internalRequest *openrtb.BidRequest
+ externalRequest *adapters.RequestData
+ response *adapters.ResponseData
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want *adapters.BidderResponse
+ errs []error
+ }{
+ {
+ name: "204 No Content indicates no bids",
+ fields: fields{Endpoint: "https://bid.avct.cloud"},
+ args: args{
+ nil,
+ nil,
+ &adapters.ResponseData{StatusCode: http.StatusNoContent},
+ },
+ want: nil,
+ errs: nil,
+ },
+ {
+ name: "Non-200 return error",
+ fields: fields{Endpoint: "https://bid.avct.cloud"},
+ args: args{
+ nil,
+ nil,
+ &adapters.ResponseData{StatusCode: http.StatusBadRequest, Body: []byte("message")},
+ },
+ want: nil,
+ errs: []error{&errortypes.BadServerResponse{Message: "received status code: 400 error: message"}},
+ },
+ {
+ name: "200 response containing banner bids",
+ fields: fields{Endpoint: "https://bid.avct.cloud"},
+ args: args{
+ nil,
+ nil,
+ &adapters.ResponseData{StatusCode: http.StatusOK, Body: validBannerBidResponseBody},
+ },
+ want: &adapters.BidderResponse{
+ Currency: "USD",
+ Bids: []*adapters.TypedBid{
+ {
+ Bid: &validBannerBid,
+ BidType: openrtb_ext.BidTypeBanner,
+ },
+ },
+ },
+ errs: nil,
+ },
+ {
+ name: "200 response containing video bids",
+ fields: fields{Endpoint: "https://bid.avct.cloud"},
+ args: args{
+ nil,
+ nil,
+ &adapters.ResponseData{StatusCode: http.StatusOK, Body: validVideoBidResponseBody},
+ },
+ want: &adapters.BidderResponse{
+ Currency: "USD",
+ Bids: []*adapters.TypedBid{
+ {
+ Bid: &validVideoBid,
+ BidType: openrtb_ext.BidTypeVideo,
+ BidVideo: &openrtb_ext.ExtBidPrebidVideo{
+ Duration: 30,
+ },
+ },
+ },
+ },
+ errs: nil,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := &AvocetAdapter{
+ Endpoint: tt.fields.Endpoint,
+ }
+ got, got1 := a.MakeBids(tt.args.internalRequest, tt.args.externalRequest, tt.args.response)
+ if !reflect.DeepEqual(got, tt.want) {
+ gotb, _ := json.Marshal(got)
+ wantb, _ := json.Marshal(tt.want)
+ t.Errorf("AvocetAdapter.MakeBids() got = %s, want %s", string(gotb), string(wantb))
+ }
+ if !reflect.DeepEqual(got1, tt.errs) {
+ t.Errorf("AvocetAdapter.MakeBids() got1 = %v, want %v", got1, tt.errs)
+ }
+ })
+ }
+}
+
+func Test_getBidType(t *testing.T) {
+ type args struct {
+ bid openrtb.Bid
+ ext avocetBidExt
+ }
+ tests := []struct {
+ name string
+ args args
+ want openrtb_ext.BidType
+ }{
+ {
+ name: "VPAID 1.0",
+ args: args{openrtb.Bid{API: openrtb.APIFrameworkVPAID10}, avocetBidExt{}},
+ want: openrtb_ext.BidTypeVideo,
+ },
+ {
+ name: "VPAID 2.0",
+ args: args{openrtb.Bid{API: openrtb.APIFrameworkVPAID20}, avocetBidExt{}},
+ want: openrtb_ext.BidTypeVideo,
+ },
+ {
+ name: "other",
+ args: args{openrtb.Bid{}, avocetBidExt{}},
+ want: openrtb_ext.BidTypeBanner,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := getBidType(tt.args.bid, tt.args.ext); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("getBidType() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+var validBannerBid = openrtb.Bid{
+ AdM: "",
+ ADomain: []string{"avocet.io"},
+ CID: "5b51e2d689654741306813a4",
+ CrID: "5b51e49634f2021f127ff7c9",
+ H: 250,
+ ID: "bc708396-9202-437b-b726-08b9864cb8b8",
+ ImpID: "test-imp-id",
+ IURL: "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg",
+ Language: "en",
+ Price: 15.64434783,
+ W: 300,
+}
+
+var validBannerBidResponseBody = []byte(`{
+ "bidid": "dd87f80c-16a0-43c8-a673-b94b3ea4d417",
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "adm": "",
+ "adomain": ["avocet.io"],
+ "cid": "5b51e2d689654741306813a4",
+ "crid": "5b51e49634f2021f127ff7c9",
+ "h": 250,
+ "id": "bc708396-9202-437b-b726-08b9864cb8b8",
+ "impid": "test-imp-id",
+ "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg",
+ "language": "en",
+ "price": 15.64434783,
+ "w": 300
+ }
+ ],
+ "seat": "TEST_SEAT_ID"
+ }
+ ]
+}`)
+
+var validVideoBid = openrtb.Bid{
+ AdM: "Avocet",
+ ADomain: []string{"avocet.io"},
+ CID: "5b51e2d689654741306813a4",
+ CrID: "5ec530e32d57fe1100f17d87",
+ H: 396,
+ ID: "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f",
+ ImpID: "dfp-ad--top-above-nav",
+ IURL: "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg",
+ Language: "en",
+ Price: 15.64434783,
+ W: 600,
+ Ext: []byte(`{"avocet":{"duration":30}}`),
+}
+
+var validVideoBidResponseBody = []byte(`{
+ "bidid": "dd87f80c-16a0-43c8-a673-b94b3ea4d417",
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "adm": "Avocet",
+ "adomain": ["avocet.io"],
+ "cid": "5b51e2d689654741306813a4",
+ "crid": "5ec530e32d57fe1100f17d87",
+ "h": 396,
+ "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f",
+ "impid": "dfp-ad--top-above-nav",
+ "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg",
+ "language": "en",
+ "price": 15.64434783,
+ "w": 600,
+ "ext": {"avocet":{"duration":30}}
+ }
+ ],
+ "seat": "TEST_SEAT_ID"
+ }
+ ]
+}`)
diff --git a/adapters/avocet/usersync.go b/adapters/avocet/usersync.go
new file mode 100644
index 00000000000..ec4f25dd952
--- /dev/null
+++ b/adapters/avocet/usersync.go
@@ -0,0 +1,12 @@
+package avocet
+
+import (
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/usersync"
+)
+
+func NewAvocetSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("avocet", 63, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/avocet/usersync_test.go b/adapters/avocet/usersync_test.go
new file mode 100644
index 00000000000..be4890df91a
--- /dev/null
+++ b/adapters/avocet/usersync_test.go
@@ -0,0 +1,35 @@
+package avocet
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAvocetSyncer(t *testing.T) {
+ syncURL := "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url=%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BUUID%7D%7D"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewAvocetSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "1",
+ Consent: "ConsentString",
+ },
+ CCPA: ccpa.Policy{
+ Value: "PrivacyString",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "https://ads.avct.cloud/getuid?&gdpr=1&gdpr_consent=ConsentString&us_privacy=PrivacyString&url=%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D1%26gdpr_consent%3DConsentString%26uid%3D%7B%7BUUID%7D%7D", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.EqualValues(t, 63, syncer.GDPRVendorID())
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go
index 22f185b9195..c7d224c31a7 100644
--- a/adapters/beachfront/beachfront.go
+++ b/adapters/beachfront/beachfront.go
@@ -4,26 +4,27 @@ import (
"encoding/json"
"errors"
"fmt"
- "github.com/PubMatic-OpenWrap/openrtb"
- "github.com/PubMatic-OpenWrap/prebid-server/adapters"
- "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
- "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
"net/http"
"reflect"
"strconv"
"strings"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+ "github.com/golang/glog"
)
const Seat = "beachfront"
const BidCapacity = 5
-const bannerEndpoint = "https://display.bfmio.com/prebid_display"
-const videoEndpoint = "https://reachms.bfmio.com/bid.json?exchange_id"
+const defaultVideoEndpoint = "https://reachms.bfmio.com/bid.json?exchange_id"
const nurlVideoEndpointSuffix = "&prebidserver"
const beachfrontAdapterName = "BF_PREBID_S2S"
-const beachfrontAdapterVersion = "0.8.0"
+const beachfrontAdapterVersion = "0.9.0"
const minBidFloor = 0.01
@@ -31,6 +32,12 @@ const DefaultVideoWidth = 300
const DefaultVideoHeight = 250
type BeachfrontAdapter struct {
+ bannerEndpoint string
+ extraInfo ExtraInfo
+}
+
+type ExtraInfo struct {
+ VideoEndpoint string `json:"video_endpoint,omitempty"`
}
type beachfrontRequests struct {
@@ -138,7 +145,7 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a
if err == nil {
reqs[0] = &adapters.RequestData{
Method: "POST",
- Uri: bannerEndpoint,
+ Uri: a.bannerEndpoint,
Body: bytes,
Headers: headers,
}
@@ -159,7 +166,7 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a
if err == nil {
reqs[j+nurlBump] = &adapters.RequestData{
Method: "POST",
- Uri: videoEndpoint + "=" + beachfrontRequests.ADMVideo[j].AppId,
+ Uri: a.extraInfo.VideoEndpoint + "=" + beachfrontRequests.ADMVideo[j].AppId,
Body: bytes,
Headers: headers,
}
@@ -178,7 +185,7 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a
bytes = append([]byte(`{"isPrebid":true,`), bytes[1:]...)
reqs[j+admBump] = &adapters.RequestData{
Method: "POST",
- Uri: videoEndpoint + "=" + beachfrontRequests.NurlVideo[j].AppId + nurlVideoEndpointSuffix,
+ Uri: a.extraInfo.VideoEndpoint + "=" + beachfrontRequests.NurlVideo[j].AppId + nurlVideoEndpointSuffix,
Body: bytes,
Headers: headers,
}
@@ -518,13 +525,13 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bids[i],
- BidType: getBidType(externalRequest),
+ BidType: a.getBidType(externalRequest),
BidVideo: &impVideo,
})
} else {
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bids[i],
- BidType: getBidType(externalRequest),
+ BidType: a.getBidType(externalRequest),
})
}
}
@@ -532,6 +539,15 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern
return bidResponse, errs
}
+func (a *BeachfrontAdapter) getBidType(externalRequest *adapters.RequestData) openrtb_ext.BidType {
+ t := strings.Split(externalRequest.Uri, "=")[0]
+ if t == a.extraInfo.VideoEndpoint {
+ return openrtb_ext.BidTypeVideo
+ }
+
+ return openrtb_ext.BidTypeBanner
+}
+
func postprocess(response *adapters.ResponseData, xtrnal openrtb.BidRequest, uri string, id string) ([]openrtb.Bid, []error) {
var beachfrontResp []beachfrontResponseSlot
var errs = make([]error, 0)
@@ -629,13 +645,13 @@ func getBeachfrontExtension(imp openrtb.Imp) (openrtb_ext.ExtImpBeachfront, erro
}
func getDomain(page string) string {
- protoUrl := strings.Split(page, "//")
+ protoURL := strings.Split(page, "//")
var domainPage string
- if len(protoUrl) > 1 {
- domainPage = protoUrl[1]
+ if len(protoURL) > 1 {
+ domainPage = protoURL[1]
} else {
- domainPage = protoUrl[0]
+ domainPage = protoURL[0]
}
return strings.Split(domainPage, "/")[0]
@@ -643,9 +659,9 @@ func getDomain(page string) string {
}
func isSecure(page string) int8 {
- protoUrl := strings.Split(page, "://")
+ protoURL := strings.Split(page, "://")
- if len(protoUrl) > 1 && protoUrl[0] == "https" {
+ if len(protoURL) > 1 && protoURL[0] == "https" {
return 1
}
@@ -663,19 +679,25 @@ func getIP(ip string) string {
return ip
}
-func getBidType(externalRequest *adapters.RequestData) openrtb_ext.BidType {
- t := strings.Split(externalRequest.Uri, "=")[0]
- if t == videoEndpoint {
- return openrtb_ext.BidTypeVideo
- }
-
- return openrtb_ext.BidTypeBanner
-}
-
func removeVideoElement(slice []beachfrontVideoRequest, s int) []beachfrontVideoRequest {
return append(slice[:s], slice[s+1:]...)
}
-func NewBeachfrontBidder() *BeachfrontAdapter {
- return &BeachfrontAdapter{}
+func NewBeachfrontBidder(bannerEndpoint string, extraAdapterInfo string) adapters.Bidder {
+ var extraInfo ExtraInfo
+
+ if len(extraAdapterInfo) == 0 {
+ extraAdapterInfo = "{\"video_endpoint\":\"" + defaultVideoEndpoint + "\"}"
+ }
+
+ if err := json.Unmarshal([]byte(extraAdapterInfo), &extraInfo); err != nil {
+ glog.Fatal("Invalid Beachfront extra adapter info: " + err.Error())
+ return nil
+ }
+
+ if extraInfo.VideoEndpoint == "" {
+ extraInfo.VideoEndpoint = defaultVideoEndpoint
+ }
+
+ return &BeachfrontAdapter{bannerEndpoint: bannerEndpoint, extraInfo: extraInfo}
}
diff --git a/adapters/beachfront/beachfront_test.go b/adapters/beachfront/beachfront_test.go
index 683b0ac90c9..905fbde6c8b 100644
--- a/adapters/beachfront/beachfront_test.go
+++ b/adapters/beachfront/beachfront_test.go
@@ -7,5 +7,5 @@ import (
)
func TestJsonSamples(t *testing.T) {
- adapterstest.RunJSONBidderTest(t, "beachfronttest", new(BeachfrontAdapter))
+ adapterstest.RunJSONBidderTest(t, "beachfronttest", NewBeachfrontBidder("https://display.bfmio.com/prebid_display", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}"))
}
diff --git a/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json b/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json
index ffcea194cdd..51ce4e9295e 100644
--- a/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json
+++ b/adapters/beachfront/beachfronttest/exemplary/minimal-banner.json
@@ -56,7 +56,7 @@
"dnt": 0,
"ua": "",
"adapterName": "BF_PREBID_S2S",
- "adapterVersion": "0.8.0",
+ "adapterVersion": "0.9.0",
"user": {
}
}
diff --git a/adapters/beachfront/beachfronttest/exemplary/simple-mix.json b/adapters/beachfront/beachfronttest/exemplary/simple-mix.json
index 6d8e483ee6d..eb5d9b07abc 100644
--- a/adapters/beachfront/beachfronttest/exemplary/simple-mix.json
+++ b/adapters/beachfront/beachfronttest/exemplary/simple-mix.json
@@ -85,7 +85,7 @@
"buyeruid": "some-buyer"
},
"adapterName": "BF_PREBID_S2S",
- "adapterVersion": "0.8.0",
+ "adapterVersion": "0.9.0",
"ip": "192.168.255.255",
"requestId": "61b87329-8790-47b7-90dd-c53ae7ce1723"
}
diff --git a/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json b/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json
index f189b2c8c79..7bdbc73cd5e 100644
--- a/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json
+++ b/adapters/beachfront/beachfronttest/supplemental/minimal-banner-empty_array-200.json
@@ -56,7 +56,7 @@
"dnt": 0,
"ua": "",
"adapterName": "BF_PREBID_S2S",
- "adapterVersion": "0.8.0",
+ "adapterVersion": "0.9.0",
"user": {
}
}
diff --git a/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json b/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json
index b610c96f58a..27b24357247 100644
--- a/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json
+++ b/adapters/beachfront/beachfronttest/supplemental/minimal-site-banner.json
@@ -56,7 +56,7 @@
"dnt": 0,
"ua": "",
"adapterName": "BF_PREBID_S2S",
- "adapterVersion": "0.8.0",
+ "adapterVersion": "0.9.0",
"user": {
}
}
diff --git a/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json b/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json
index d47393b7caf..ea38d7adae7 100644
--- a/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json
+++ b/adapters/beachfront/beachfronttest/supplemental/mobile-banner.json
@@ -87,7 +87,7 @@
},
"adapterName":"BF_PREBID_S2S",
- "adapterVersion":"0.8.0",
+ "adapterVersion":"0.9.0",
"ip":"192.168.255.255",
"requestId":"763e3312-19d5-4b07-a61d-890147e863a1"
}
diff --git a/adapters/beachfront/beachfronttest/supplemental/multi-banner.json b/adapters/beachfront/beachfronttest/supplemental/multi-banner.json
index c4120787852..46699511a9c 100644
--- a/adapters/beachfront/beachfronttest/supplemental/multi-banner.json
+++ b/adapters/beachfront/beachfronttest/supplemental/multi-banner.json
@@ -96,7 +96,7 @@
"dnt": 1,
"ua": "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16",
"adapterName": "BF_PREBID_S2S",
- "adapterVersion": "0.8.0",
+ "adapterVersion": "0.9.0",
"user": {
"buyeruid": "some-buyer",
"id": "some-user"
diff --git a/adapters/beachfront/usersync.go b/adapters/beachfront/usersync.go
index cfb099a80c6..f355697f4e0 100644
--- a/adapters/beachfront/usersync.go
+++ b/adapters/beachfront/usersync.go
@@ -7,6 +7,12 @@ import (
"github.com/PubMatic-OpenWrap/prebid-server/usersync"
)
+var VENDOR_ID uint16 = 335
+
func NewBeachfrontSyncer(temp *template.Template) usersync.Usersyncer {
- return adapters.NewSyncer("beachfront", 0, temp, adapters.SyncTypeIframe)
+ return adapters.NewSyncer(
+ "beachfront",
+ VENDOR_ID,
+ temp,
+ adapters.SyncTypeIframe)
}
diff --git a/adapters/beachfront/usersync_test.go b/adapters/beachfront/usersync_test.go
index 38efd0a54d7..e0aed3f5479 100644
--- a/adapters/beachfront/usersync_test.go
+++ b/adapters/beachfront/usersync_test.go
@@ -30,6 +30,6 @@ func TestBeachfrontSyncer(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "https://sync.bfmio.com/sync_s2s?gdpr=A&us_privacy=C&url=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%5Bio_cid%5D", syncInfo.URL)
assert.Equal(t, "iframe", syncInfo.Type)
- assert.EqualValues(t, 0, syncer.GDPRVendorID())
+ assert.EqualValues(t, uint16(335), syncer.GDPRVendorID())
assert.Equal(t, false, syncInfo.SupportCORS)
}
diff --git a/adapters/beintoo/beintoo.go b/adapters/beintoo/beintoo.go
new file mode 100644
index 00000000000..77b6b260700
--- /dev/null
+++ b/adapters/beintoo/beintoo.go
@@ -0,0 +1,222 @@
+package beintoo
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+type BeintooAdapter struct {
+ endpoint string
+}
+
+func (a *BeintooAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ var errors []error
+
+ if len(request.Imp) == 0 {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("No Imps in Bid Request"),
+ }}
+ }
+
+ if errors := preprocess(request); errors != nil && len(errors) > 0 {
+ return nil, append(errors, &errortypes.BadInput{
+ Message: fmt.Sprintf("Error in preprocess of Imp, err: %s", errors),
+ })
+ }
+
+ data, err := json.Marshal(request)
+ if err != nil {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("Error in packaging request to JSON"),
+ }}
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ if request.Device != nil {
+ addHeaderIfNonEmpty(headers, "User-Agent", request.Device.UA)
+ addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IP)
+ addHeaderIfNonEmpty(headers, "Accept-Language", request.Device.Language)
+ if request.Device.DNT != nil {
+ addHeaderIfNonEmpty(headers, "DNT", strconv.Itoa(int(*request.Device.DNT)))
+ }
+ }
+ if request.Site != nil {
+ addHeaderIfNonEmpty(headers, "Referer", request.Site.Page)
+ }
+
+ return []*adapters.RequestData{{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: data,
+ Headers: headers,
+ }}, errors
+}
+
+func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpBeintoo, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: err.Error(),
+ }
+ }
+
+ var beintooExt openrtb_ext.ExtImpBeintoo
+ if err := json.Unmarshal(bidderExt.Bidder, &beintooExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("ignoring imp id=%s, invalid ImpExt", imp.ID),
+ }
+ }
+
+ tagIDValidation, err := strconv.ParseInt(beintooExt.TagID, 10, 64)
+ if err != nil || tagIDValidation == 0 {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("ignoring imp id=%s, invalid tagid must be a String of numbers", imp.ID),
+ }
+ }
+
+ return &beintooExt, nil
+}
+
+func buildImpBanner(imp *openrtb.Imp) error {
+ imp.Ext = nil
+
+ if imp.Banner == nil {
+ return &errortypes.BadInput{
+ Message: fmt.Sprintf("Request needs to include a Banner object"),
+ }
+ }
+
+ bannerCopy := *imp.Banner
+ banner := &bannerCopy
+
+ if banner.W == nil && banner.H == nil {
+ if len(banner.Format) == 0 {
+ return &errortypes.BadInput{
+ Message: fmt.Sprintf("Need at least one size to build request"),
+ }
+ }
+ format := banner.Format[0]
+ banner.Format = banner.Format[1:]
+ banner.W = &format.W
+ banner.H = &format.H
+ imp.Banner = banner
+ }
+
+ return nil
+}
+
+// Add Beintoo required properties to Imp object
+func addImpProps(imp *openrtb.Imp, secure *int8, BeintooExt *openrtb_ext.ExtImpBeintoo) {
+ imp.TagID = BeintooExt.TagID
+ imp.Secure = secure
+
+ if BeintooExt.BidFloor != "" {
+ bidFloor, err := strconv.ParseFloat(BeintooExt.BidFloor, 64)
+ if err != nil {
+ bidFloor = 0
+ }
+
+ if bidFloor > 0 {
+ imp.BidFloor = bidFloor
+ }
+ }
+
+ return
+}
+
+// Adding header fields to request header
+func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) {
+ if len(headerValue) > 0 {
+ headers.Add(headerName, headerValue)
+ }
+}
+
+// Handle request errors and formatting to be sent to Beintoo
+func preprocess(request *openrtb.BidRequest) []error {
+ errors := make([]error, 0, len(request.Imp))
+ resImps := make([]openrtb.Imp, 0, len(request.Imp))
+ secure := int8(0)
+
+ if request.Site != nil && request.Site.Page != "" {
+ pageURL, err := url.Parse(request.Site.Page)
+ if err == nil && pageURL.Scheme == "https" {
+ secure = int8(1)
+ }
+ }
+
+ for _, imp := range request.Imp {
+ beintooExt, err := unpackImpExt(&imp)
+ if err != nil {
+ errors = append(errors, err)
+ return errors
+ }
+
+ addImpProps(&imp, &secure, beintooExt)
+
+ if err := buildImpBanner(&imp); err != nil {
+ errors = append(errors, err)
+ return errors
+ }
+ resImps = append(resImps, imp)
+ }
+
+ request.Imp = resImps
+
+ return errors
+}
+
+// MakeBids make the bids for the bid response.
+func (a *BeintooAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+
+ if response.StatusCode == http.StatusNoContent {
+ // no bid response
+ return nil, nil
+ }
+
+ if response.StatusCode != http.StatusOK {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Invalid Status Returned: %d. Run with request.debug = 1 for more info", response.StatusCode),
+ }}
+ }
+
+ var bidResp openrtb.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unable to unpackage bid response. Error: %s", err.Error()),
+ }}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+
+ for _, sb := range bidResp.SeatBid {
+ for i := range sb.Bid {
+ sb.Bid[i].ImpID = sb.Bid[i].ID
+
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &sb.Bid[i],
+ BidType: "banner",
+ })
+ }
+ }
+
+ return bidResponse, nil
+
+}
+
+func NewBeintooBidder(endpoint string) *BeintooAdapter {
+ return &BeintooAdapter{
+ endpoint: endpoint,
+ }
+}
diff --git a/adapters/beintoo/beintoo_test.go b/adapters/beintoo/beintoo_test.go
new file mode 100644
index 00000000000..d5a61c26209
--- /dev/null
+++ b/adapters/beintoo/beintoo_test.go
@@ -0,0 +1,12 @@
+package beintoo
+
+import (
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+ beintooAdapter := NewBeintooBidder("https://ib.beintoo.com")
+ adapterstest.RunJSONBidderTest(t, "beintootest", beintooAdapter)
+}
diff --git a/adapters/beintoo/beintootest/exemplary/minimal-banner.json b/adapters/beintoo/beintootest/exemplary/minimal-banner.json
new file mode 100644
index 00000000000..60e481c507c
--- /dev/null
+++ b/adapters/beintoo/beintootest/exemplary/minimal-banner.json
@@ -0,0 +1,117 @@
+{
+ "mockBidRequest": {
+ "id": "some_test_auction",
+ "imp": [{
+ "id": "some_test_ad_id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "tagid": "25251"
+ }
+ }
+ }],
+ "device": {
+ "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+ "ip": "123.123.123.123",
+ "dnt": 1
+ },
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site?with=some¶meters=here"
+ }
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://ib.beintoo.com",
+ "headers": {
+ "Accept": [
+ "application/json"
+ ],
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ],
+ "Referer": [
+ "http://www.publisher.com/awesome/site?with=some¶meters=here"
+ ],
+ "Dnt": [
+ "1"
+ ],
+ "User-Agent": [
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36"
+ ]
+ },
+ "body": {
+ "id": "some_test_auction",
+ "imp": [{
+ "id": "some_test_ad_id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }],
+ "w": 300,
+ "h": 250
+ },
+ "tagid": "25251",
+ "secure": 0
+ }],
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site?with=some¶meters=here"
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+ "ip": "123.123.123.123",
+ "dnt": 1
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "some_test_auction",
+ "seatbid": [{
+ "seat": "12356",
+ "bid": [{
+ "adm": ""
+const adSourceURL = "https://ad.yieldlab.net/d/%v/%v/%v?%v"
+const creativeID = "%v%v%v"
diff --git a/adapters/yieldlab/params_test.go b/adapters/yieldlab/params_test.go
new file mode 100644
index 00000000000..f66121e35e8
--- /dev/null
+++ b/adapters/yieldlab/params_test.go
@@ -0,0 +1,63 @@
+package yieldlab
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/yieldlab.json
+//
+// These also validate the format of the external API: request.imp[i].ext.yieldlab
+
+// TestValidParams makes sure that the yieldlab schema accepts all imp.ext fields which we intend to support.
+func TestValidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, validParam := range validParams {
+ if err := validator.Validate(openrtb_ext.BidderYieldlab, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected yieldlab params: %s", validParam)
+ }
+ }
+}
+
+// TestInvalidParams makes sure that the yieldlab schema rejects all the imp.ext fields we don't support.
+func TestInvalidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, invalidParam := range invalidParams {
+ if err := validator.Validate(openrtb_ext.BidderYieldlab, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"adslotId": "123","supplyId":"23456","adSize":"100x100"}`,
+ `{"adslotId": "123","supplyId":"23456","adSize":"100x100","extId":"asdf"}`,
+ `{"adslotId": "123","supplyId":"23456","adSize":"100x100","extId":"asdf","targeting":{"a":"b"}}`,
+ `{"adslotId": "123","supplyId":"23456","adSize":"100x100","targeting":{"a":"b"}}`,
+ `{"adslotId": "123","supplyId":"23456","adSize":"100x100","targeting":{"a":"b"}}`,
+}
+
+var invalidParams = []string{
+ `{"supplyId":"23456","adSize":"100x100"}`,
+ `{"adslotId": "123","adSize":"100x100","extId":"asdf"}`,
+ `{"adslotId": "123","supplyId":"23456","extId":"asdf","targeting":{"a":"b"}}`,
+ `{"adslotId": "123","supplyId":"23456"}`,
+ `{"adSize":"100x100","supplyId":"23456"}`,
+ `{"adslotId": "123","adSize":"100x100"}`,
+ `{"supplyId":"23456"}`,
+ `{"adslotId": "123"}`,
+ `{}`,
+ `[]`,
+ `{"a":"b"}`,
+ `null`,
+}
diff --git a/adapters/yieldlab/types.go b/adapters/yieldlab/types.go
new file mode 100644
index 00000000000..90612700713
--- /dev/null
+++ b/adapters/yieldlab/types.go
@@ -0,0 +1,29 @@
+package yieldlab
+
+import (
+ "strconv"
+ "time"
+)
+
+type bidResponse struct {
+ ID uint64 `json:"id"`
+ Price uint `json:"price"`
+ Advertiser string `json:"advertiser"`
+ Adsize string `json:"adsize"`
+ Pid uint64 `json:"pid"`
+ Did uint64 `json:"did"`
+ Pvid string `json:"pvid"`
+}
+
+type cacheBuster func() string
+
+type weekGenerator func() string
+
+var defaultCacheBuster cacheBuster = func() string {
+ return strconv.FormatInt(time.Now().Unix(), 10)
+}
+
+var defaultWeekGenerator weekGenerator = func() string {
+ _, week := time.Now().ISOWeek()
+ return strconv.Itoa(week)
+}
diff --git a/adapters/yieldlab/usersync.go b/adapters/yieldlab/usersync.go
new file mode 100644
index 00000000000..a0462e19e6e
--- /dev/null
+++ b/adapters/yieldlab/usersync.go
@@ -0,0 +1,12 @@
+package yieldlab
+
+import (
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/usersync"
+)
+
+func NewYieldlabSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("yieldlab", 70, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/yieldlab/usersync_test.go b/adapters/yieldlab/usersync_test.go
new file mode 100644
index 00000000000..cdca7f9f417
--- /dev/null
+++ b/adapters/yieldlab/usersync_test.go
@@ -0,0 +1,26 @@
+package yieldlab
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr"
+)
+
+func TestYieldlabSyncer(t *testing.T) {
+ temp := template.Must(template.New("sync-template").Parse("https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25"))
+ syncer := NewYieldlabSyncer(temp)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "0",
+ },
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr=0&gdpr_consent=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%25%25YL_UID%25%25", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.EqualValues(t, 70, syncer.GDPRVendorID())
+ assert.False(t, syncInfo.SupportCORS)
+}
diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go
new file mode 100644
index 00000000000..f9b1f136915
--- /dev/null
+++ b/adapters/yieldlab/yieldlab.go
@@ -0,0 +1,314 @@
+package yieldlab
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/url"
+ "path"
+ "strconv"
+ "strings"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "golang.org/x/text/currency"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+// YieldlabAdapter connects the Yieldlab API to prebid server
+type YieldlabAdapter struct {
+ endpoint string
+ cacheBuster cacheBuster
+ getWeek weekGenerator
+}
+
+// NewYieldlabBidder returns a new YieldlabBidder instance
+func NewYieldlabBidder(endpoint string) *YieldlabAdapter {
+ return &YieldlabAdapter{
+ endpoint: endpoint,
+ cacheBuster: defaultCacheBuster,
+ getWeek: defaultWeekGenerator,
+ }
+}
+
+// Builds endpoint url based on adapter-specific pub settings from imp.ext
+func (a *YieldlabAdapter) makeEndpointURL(req *openrtb.BidRequest, params *openrtb_ext.ExtImpYieldlab) (string, error) {
+ uri, err := url.Parse(a.endpoint)
+ if err != nil {
+ return "", fmt.Errorf("failed to parse yieldlab endpoint: %v", err)
+ }
+
+ uri.Path = path.Join(uri.Path, params.AdslotID)
+ q := uri.Query()
+ q.Set("content", "json")
+ q.Set("pvid", "true")
+ q.Set("ts", a.cacheBuster())
+ q.Set("t", a.makeTargetingValues(params))
+
+ if req.User != nil && req.User.BuyerUID != "" {
+ q.Set("ids", "ylid:"+req.User.BuyerUID)
+ }
+
+ if req.Device != nil {
+ q.Set("yl_rtb_ifa", req.Device.IFA)
+ q.Set("yl_rtb_devicetype", fmt.Sprintf("%v", req.Device.DeviceType))
+
+ if req.Device.ConnectionType != nil {
+ q.Set("yl_rtb_connectiontype", fmt.Sprintf("%v", req.Device.ConnectionType.Val()))
+ }
+
+ if req.Device.Geo != nil {
+ q.Set("lat", fmt.Sprintf("%v", req.Device.Geo.Lat))
+ q.Set("lon", fmt.Sprintf("%v", req.Device.Geo.Lon))
+ }
+ }
+
+ if req.App != nil {
+ q.Set("pubappname", req.App.Name)
+ q.Set("pubbundlename", req.App.Bundle)
+ }
+
+ gdpr, consent, err := a.getGDPR(req)
+ if err != nil {
+ return "", err
+ }
+ if gdpr != "" && consent != "" {
+ q.Set("gdpr", gdpr)
+ q.Set("consent", consent)
+ }
+
+ uri.RawQuery = q.Encode()
+
+ return uri.String(), nil
+}
+
+func (a *YieldlabAdapter) getGDPR(request *openrtb.BidRequest) (string, string, error) {
+ gdpr := ""
+ var extRegs openrtb_ext.ExtRegs
+ if request.Regs != nil {
+ if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil {
+ return "", "", fmt.Errorf("failed to parse ExtRegs in Yieldlab GDPR check: %v", err)
+ }
+ if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) {
+ gdpr = strconv.Itoa(int(*extRegs.GDPR))
+ }
+ }
+
+ consent := ""
+ if request.User != nil && request.User.Ext != nil {
+ var extUser openrtb_ext.ExtUser
+ if err := json.Unmarshal(request.User.Ext, &extUser); err != nil {
+ return "", "", fmt.Errorf("failed to parse ExtUser in Yieldlab GDPR check: %v", err)
+ }
+ consent = extUser.Consent
+ }
+
+ return gdpr, consent, nil
+}
+
+func (a *YieldlabAdapter) makeTargetingValues(params *openrtb_ext.ExtImpYieldlab) string {
+ values := url.Values{}
+ for k, v := range params.Targeting {
+ values.Set(k, v)
+ }
+ return values.Encode()
+}
+
+func (a *YieldlabAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ if len(request.Imp) == 0 {
+ return nil, []error{fmt.Errorf("invalid request %+v, no Impressions given", request)}
+ }
+
+ bidURL, err := a.makeEndpointURL(request, a.mergeParams(a.parseRequest(request)))
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ headers := http.Header{}
+ headers.Add("Accept", "application/json")
+ if request.Site != nil {
+ headers.Add("Referer", request.Site.Page)
+ }
+ if request.Device != nil {
+ headers.Add("User-Agent", request.Device.UA)
+ headers.Add("X-Forwarded-For", request.Device.IP)
+ }
+ if request.User != nil {
+ headers.Add("Cookie", "id="+request.User.BuyerUID)
+ }
+
+ return []*adapters.RequestData{{
+ Method: "GET",
+ Uri: bidURL,
+ Headers: headers,
+ }}, nil
+}
+
+// parseRequest extracts the Yieldlab request information from the request
+func (a *YieldlabAdapter) parseRequest(request *openrtb.BidRequest) []*openrtb_ext.ExtImpYieldlab {
+ params := make([]*openrtb_ext.ExtImpYieldlab, 0)
+
+ for i := 0; i < len(request.Imp); i++ {
+ bidderExt := new(adapters.ExtImpBidder)
+ if err := json.Unmarshal(request.Imp[i].Ext, bidderExt); err != nil {
+ continue
+ }
+
+ yieldlabExt := new(openrtb_ext.ExtImpYieldlab)
+ if err := json.Unmarshal(bidderExt.Bidder, yieldlabExt); err != nil {
+ continue
+ }
+
+ params = append(params, yieldlabExt)
+ }
+
+ return params
+}
+
+func (a *YieldlabAdapter) mergeParams(params []*openrtb_ext.ExtImpYieldlab) *openrtb_ext.ExtImpYieldlab {
+ var adSlotIds []string
+ targeting := make(map[string]string)
+
+ for _, p := range params {
+ adSlotIds = append(adSlotIds, p.AdslotID)
+ for k, v := range p.Targeting {
+ targeting[k] = v
+ }
+ }
+
+ return &openrtb_ext.ExtImpYieldlab{
+ AdslotID: strings.Join(adSlotIds, adSlotIdSeparator),
+ Targeting: targeting,
+ }
+}
+
+// MakeBids make the bids for the bid response.
+func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if response.StatusCode != 200 {
+ return nil, []error{
+ &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("failed to resolve bids from yieldlab response: Unexpected response code %v", response.StatusCode),
+ },
+ }
+ }
+
+ bids := make([]*bidResponse, 0)
+ if err := json.Unmarshal(response.Body, &bids); err != nil {
+ return nil, []error{
+ &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("failed to parse bids response from yieldlab: %v", err),
+ },
+ }
+ }
+
+ params := a.parseRequest(internalRequest)
+
+ bidderResponse := &adapters.BidderResponse{
+ Currency: currency.EUR.String(),
+ Bids: []*adapters.TypedBid{},
+ }
+
+ for i, bid := range bids {
+ width, height, err := splitSize(bid.Adsize)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ req := a.findBidReq(bid.ID, params)
+ if req == nil {
+ return nil, []error{
+ fmt.Errorf("failed to find yieldlab request for adslotID %v. This is most likely a programming issue", bid.ID),
+ }
+ }
+
+ var bidType openrtb_ext.BidType
+ responseBid := &openrtb.Bid{
+ ID: strconv.FormatUint(bid.ID, 10),
+ Price: float64(bid.Price) / 100,
+ ImpID: internalRequest.Imp[i].ID,
+ CrID: a.makeCreativeID(req, bid),
+ DealID: strconv.FormatUint(bid.Pid, 10),
+ W: width,
+ H: height,
+ }
+
+ if internalRequest.Imp[i].Video != nil {
+ bidType = openrtb_ext.BidTypeVideo
+ responseBid.NURL = a.makeAdSourceURL(internalRequest, req, bid)
+
+ } else if internalRequest.Imp[i].Banner != nil {
+ bidType = openrtb_ext.BidTypeBanner
+ responseBid.AdM = a.makeBannerAdSource(internalRequest, req, bid)
+ } else {
+ // Yieldlab adapter currently doesn't support Audio and Native ads
+ continue
+ }
+
+ bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
+ BidType: bidType,
+ Bid: responseBid,
+ })
+ }
+
+ return bidderResponse, nil
+}
+
+func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtImpYieldlab) *openrtb_ext.ExtImpYieldlab {
+ slotIdStr := strconv.FormatUint(adslotID, 10)
+ for _, p := range params {
+ if p.AdslotID == slotIdStr {
+ return p
+ }
+ }
+
+ return nil
+}
+
+func (a *YieldlabAdapter) makeBannerAdSource(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string {
+ return fmt.Sprintf(adSourceBanner, a.makeAdSourceURL(req, ext, res))
+}
+
+func (a *YieldlabAdapter) makeAdSourceURL(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string {
+ val := url.Values{}
+ val.Set("ts", a.cacheBuster())
+ val.Set("id", ext.ExtId)
+ val.Set("pvid", res.Pvid)
+
+ if req.User != nil {
+ val.Set("ids", "ylid:"+req.User.BuyerUID)
+ }
+
+ gdpr, consent, err := a.getGDPR(req)
+ if err == nil && gdpr != "" && consent != "" {
+ val.Set("gdpr", gdpr)
+ val.Set("consent", consent)
+ }
+
+ return fmt.Sprintf(adSourceURL, ext.AdslotID, ext.SupplyID, res.Adsize, val.Encode())
+}
+
+func (a *YieldlabAdapter) makeCreativeID(req *openrtb_ext.ExtImpYieldlab, bid *bidResponse) string {
+ return fmt.Sprintf(creativeID, req.AdslotID, bid.Pid, a.getWeek())
+}
+
+func splitSize(size string) (uint64, uint64, error) {
+ sizeParts := strings.Split(size, adsizeSeparator)
+ if len(sizeParts) != 2 {
+ return 0, 0, nil
+ }
+
+ width, err := strconv.ParseUint(sizeParts[0], 10, 64)
+ if err != nil {
+ return 0, 0, fmt.Errorf("failed to parse yieldlab adsize: %v", err)
+ }
+
+ height, err := strconv.ParseUint(sizeParts[1], 10, 64)
+ if err != nil {
+ return 0, 0, fmt.Errorf("failed to parse yieldlab adsize: %v", err)
+ }
+
+ return width, height, nil
+
+}
diff --git a/adapters/yieldlab/yieldlab_test.go b/adapters/yieldlab/yieldlab_test.go
new file mode 100644
index 00000000000..274d00e7cd2
--- /dev/null
+++ b/adapters/yieldlab/yieldlab_test.go
@@ -0,0 +1,128 @@
+package yieldlab
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+)
+
+const testURL = "https://ad.yieldlab.net/testing/"
+
+var testCacheBuster cacheBuster = func() string {
+ return "testing"
+}
+
+var testWeekGenerator weekGenerator = func() string {
+ return "33"
+}
+
+func newTestYieldlabBidder(endpoint string) *YieldlabAdapter {
+ return &YieldlabAdapter{
+ endpoint: endpoint,
+ cacheBuster: testCacheBuster,
+ getWeek: testWeekGenerator,
+ }
+}
+
+func TestNewYieldlabBidder(t *testing.T) {
+ bid := NewYieldlabBidder(testURL)
+ assert.NotNil(t, bid)
+ assert.Equal(t, bid.endpoint, testURL)
+ assert.NotNil(t, bid.cacheBuster)
+ assert.NotNil(t, bid.getWeek)
+}
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "yieldlabtest", newTestYieldlabBidder(testURL))
+}
+
+func Test_splitSize(t *testing.T) {
+ type args struct {
+ size string
+ }
+ tests := []struct {
+ name string
+ args args
+ want uint64
+ want1 uint64
+ wantErr bool
+ }{
+ {
+ name: "valid",
+ args: args{
+ size: "300x800",
+ },
+ want: 300,
+ want1: 800,
+ wantErr: false,
+ },
+ {
+ name: "empty",
+ args: args{
+ size: "",
+ },
+ want: 0,
+ want1: 0,
+ wantErr: false,
+ },
+ {
+ name: "invalid",
+ args: args{
+ size: "test",
+ },
+ want: 0,
+ want1: 0,
+ wantErr: false,
+ },
+ {
+ name: "invalid_height",
+ args: args{
+ size: "200xtest",
+ },
+ want: 0,
+ want1: 0,
+ wantErr: true,
+ },
+ {
+ name: "invalid_width",
+ args: args{
+ size: "testx200",
+ },
+ want: 0,
+ want1: 0,
+ wantErr: true,
+ },
+ {
+ name: "invalid_separator",
+ args: args{
+ size: "200y200",
+ },
+ want: 0,
+ want1: 0,
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, got1, err := splitSize(tt.args.size)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("splitSize() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("splitSize() got = %v, want %v", got, tt.want)
+ }
+ if got1 != tt.want1 {
+ t.Errorf("splitSize() got1 = %v, want %v", got1, tt.want1)
+ }
+ })
+ }
+}
+
+func TestYieldlabAdapter_makeEndpointURL_invalidEndpoint(t *testing.T) {
+ bid := NewYieldlabBidder("test$:/something§")
+ _, err := bid.makeEndpointURL(nil, nil)
+ assert.Error(t, err)
+}
diff --git a/adapters/yieldlab/yieldlabtest/exemplary/banner.json b/adapters/yieldlab/yieldlabtest/exemplary/banner.json
new file mode 100644
index 00000000000..8dd94404097
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/exemplary/banner.json
@@ -0,0 +1,111 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "adslotId": "12345",
+ "supplyId": "123456789",
+ "adSize": "728x90",
+ "targeting": {
+ "key1": "value1",
+ "key2": "value2"
+ },
+ "extId": "abc"
+ }
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c"
+ },
+ "device": {
+ "ifa": "hello-ads",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "geo": {
+ "lat": 51.499488,
+ "lon": -0.128953
+ },
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+ "ip": "169.254.13.37",
+ "h": 1098,
+ "w": 814
+ },
+ "site": {
+ "id": "fake-site-id",
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://localhost:9090/gdpr.html"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Accept": [
+ "application/json"
+ ],
+ "Cookie": [
+ "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+ ],
+ "Referer": [
+ "http://localhost:9090/gdpr.html"
+ ],
+ "User-Agent": [
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+ ],
+ "X-Forwarded-For": [
+ "169.254.13.37"
+ ]
+ },
+ "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [
+ {
+ "id": 12345,
+ "price": 201,
+ "advertiser": "yieldlab",
+ "adsize": "728x90",
+ "pid": 1234,
+ "did": 5678,
+ "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5"
+ }
+ ]
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "crid": "12345123433",
+ "dealid": "1234",
+ "id": "12345",
+ "impid": "test-imp-id",
+ "price": 2.01,
+ "w": 728,
+ "h": 90
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json
new file mode 100644
index 00000000000..381ba688e09
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json
@@ -0,0 +1,119 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "adslotId": "12345",
+ "supplyId": "123456789",
+ "adSize": "728x90",
+ "targeting": {
+ "key1": "value1",
+ "key2": "value2"
+ },
+ "extId": "abc"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ifa": "hello-ads",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "geo": {
+ "lat": 51.499488,
+ "lon": -0.128953
+ },
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+ "ip": "169.254.13.37",
+ "h": 1098,
+ "w": 814
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "site": {
+ "id": "fake-site-id",
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://localhost:9090/gdpr.html"
+ },
+ "user": {
+ "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c",
+ "ext": {
+ "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Accept": [
+ "application/json"
+ ],
+ "Cookie": [
+ "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+ ],
+ "Referer": [
+ "http://localhost:9090/gdpr.html"
+ ],
+ "User-Agent": [
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+ ],
+ "X-Forwarded-For": [
+ "169.254.13.37"
+ ]
+ },
+ "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [
+ {
+ "id": 12345,
+ "price": 201,
+ "advertiser": "yieldlab",
+ "adsize": "728x90",
+ "pid": 1234,
+ "did": 5678,
+ "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5"
+ }
+ ]
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "crid": "12345123433",
+ "dealid": "1234",
+ "id": "12345",
+ "impid": "test-imp-id",
+ "price": 2.01,
+ "w": 728,
+ "h": 90
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video.json b/adapters/yieldlab/yieldlabtest/exemplary/video.json
new file mode 100644
index 00000000000..9e970ae79b5
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/exemplary/video.json
@@ -0,0 +1,136 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "adslotId": "12345",
+ "supplyId": "123456789",
+ "adSize": "728x90",
+ "targeting": {
+ "key1": "value1",
+ "key2": "value2"
+ },
+ "extId": "abc"
+ }
+ },
+ "video": {
+ "context": "instream",
+ "mimes": [
+ "video/mp4"
+ ],
+ "playerSize": [
+ [
+ 400,
+ 600
+ ]
+ ],
+ "minduration": 1,
+ "maxduration": 2,
+ "protocols": [
+ 1,
+ 2
+ ],
+ "w": 1,
+ "h": 2,
+ "startdelay": 1,
+ "placement": 1,
+ "playbackmethod": [
+ 2
+ ]
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c"
+ },
+ "device": {
+ "ifa": "hello-ads",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "geo": {
+ "lat": 51.499488,
+ "lon": -0.128953
+ },
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+ "ip": "169.254.13.37",
+ "h": 1098,
+ "w": 814
+ },
+ "site": {
+ "id": "fake-site-id",
+ "publisher": {
+ "id": "1"
+ },
+ "page": "http://localhost:9090/gdpr.html"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Accept": [
+ "application/json"
+ ],
+ "Cookie": [
+ "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+ ],
+ "Referer": [
+ "http://localhost:9090/gdpr.html"
+ ],
+ "User-Agent": [
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+ ],
+ "X-Forwarded-For": [
+ "169.254.13.37"
+ ]
+ },
+ "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [
+ {
+ "id": 12345,
+ "price": 201,
+ "advertiser": "yieldlab",
+ "adsize": "728x90",
+ "pid": 1234,
+ "did": 5678,
+ "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5"
+ }
+ ]
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "crid": "12345123433",
+ "dealid": "1234",
+ "id": "12345",
+ "impid": "test-imp-id",
+ "nurl": "https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing",
+ "price": 2.01,
+ "w": 728,
+ "h": 90
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video_app.json b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json
new file mode 100644
index 00000000000..67d526b3400
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json
@@ -0,0 +1,136 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "adslotId": "12345",
+ "supplyId": "123456789",
+ "adSize": "728x90",
+ "targeting": {
+ "key1": "value1",
+ "key2": "value2"
+ },
+ "extId": "abc"
+ }
+ },
+ "video": {
+ "context": "instream",
+ "mimes": [
+ "video/mp4"
+ ],
+ "playerSize": [
+ [
+ 400,
+ 600
+ ]
+ ],
+ "minduration": 1,
+ "maxduration": 2,
+ "protocols": [
+ 1,
+ 2
+ ],
+ "w": 1,
+ "h": 2,
+ "startdelay": 1,
+ "placement": 1,
+ "playbackmethod": [
+ 2
+ ]
+ }
+ }
+ ],
+ "user": {
+ "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c"
+ },
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "device": {
+ "ifa": "hello-ads",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "geo": {
+ "lat": 51.499488,
+ "lon": -0.128953
+ },
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+ "ip": "169.254.13.37",
+ "h": 1098,
+ "w": 814
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Accept": [
+ "application/json"
+ ],
+ "Cookie": [
+ "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+ ],
+ "User-Agent": [
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+ ],
+ "X-Forwarded-For": [
+ "169.254.13.37"
+ ]
+ },
+ "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pubappname=Awesome+App&pubbundlename=com.app.awesome&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": [
+ {
+ "id": 12345,
+ "price": 201,
+ "advertiser": "yieldlab",
+ "adsize": "728x90",
+ "pid": 1234,
+ "did": 5678,
+ "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5"
+ }
+ ]
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "crid": "12345123433",
+ "dealid": "1234",
+ "id": "12345",
+ "impid": "test-imp-id",
+ "nurl": "https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing",
+ "price": 2.01,
+ "w": 728,
+ "h": 90
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/yieldmo/usersync.go b/adapters/yieldmo/usersync.go
index 16fa10e5b78..25d65f229a2 100644
--- a/adapters/yieldmo/usersync.go
+++ b/adapters/yieldmo/usersync.go
@@ -8,5 +8,5 @@ import (
)
func NewYieldmoSyncer(temp *template.Template) usersync.Usersyncer {
- return adapters.NewSyncer("yieldmo", 0, temp, adapters.SyncTypeRedirect)
+ return adapters.NewSyncer("yieldmo", 173, temp, adapters.SyncTypeRedirect)
}
diff --git a/adapters/yieldmo/usersync_test.go b/adapters/yieldmo/usersync_test.go
index 10cba77a060..1212efdb878 100644
--- a/adapters/yieldmo/usersync_test.go
+++ b/adapters/yieldmo/usersync_test.go
@@ -25,6 +25,6 @@ func TestYieldmoSyncer(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "//ads.yieldmo.com/pbsync?gdpr=0&gdpr_consent=&us_privacy=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL)
assert.Equal(t, "redirect", syncInfo.Type)
- assert.EqualValues(t, 0, syncer.GDPRVendorID())
+ assert.EqualValues(t, 173, syncer.GDPRVendorID())
assert.False(t, syncInfo.SupportCORS)
}
diff --git a/adapters/yieldone/params_test.go b/adapters/yieldone/params_test.go
new file mode 100644
index 00000000000..e0142334d6e
--- /dev/null
+++ b/adapters/yieldone/params_test.go
@@ -0,0 +1,48 @@
+package yieldone
+
+import (
+ "encoding/json"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+ "testing"
+)
+
+func TestValidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, validParam := range validParams {
+ if err := validator.Validate(openrtb_ext.BidderYieldone, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected Yieldone params: %s", validParam)
+ }
+ }
+}
+
+func TestInvalidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, invalidParam := range invalidParams {
+ if err := validator.Validate(openrtb_ext.BidderYieldone, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"placementId": "123"}`,
+}
+
+var invalidParams = []string{
+ `null`,
+ `nil`,
+ ``,
+ `[]`,
+ `true`,
+ `2`,
+ `{"invalid_param": "123"}`,
+ `{"placementId": 123}`,
+}
diff --git a/adapters/yieldone/usersync.go b/adapters/yieldone/usersync.go
new file mode 100644
index 00000000000..333550aa775
--- /dev/null
+++ b/adapters/yieldone/usersync.go
@@ -0,0 +1,12 @@
+package yieldone
+
+import (
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/usersync"
+)
+
+func NewYieldoneSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("yieldone", 0, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/yieldone/usersync_test.go b/adapters/yieldone/usersync_test.go
new file mode 100644
index 00000000000..730f9103017
--- /dev/null
+++ b/adapters/yieldone/usersync_test.go
@@ -0,0 +1,30 @@
+package yieldone
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestYieldoneSyncer(t *testing.T) {
+ syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewYieldoneSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "0",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.EqualValues(t, 0, syncer.GDPRVendorID())
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go
new file mode 100644
index 00000000000..7b0f35a7dc7
--- /dev/null
+++ b/adapters/yieldone/yieldone.go
@@ -0,0 +1,144 @@
+package yieldone
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+)
+
+type YieldoneAdapter struct {
+ endpoint string
+}
+
+// MakeRequests makes the HTTP requests which should be made to fetch bids.
+func (a *YieldoneAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ var errors = make([]error, 0)
+
+ var validImps []openrtb.Imp
+ for i := 0; i < len(request.Imp); i++ {
+ if err := preprocess(&request.Imp[i]); err == nil {
+ validImps = append(validImps, request.Imp[i])
+ } else {
+ errors = append(errors, err)
+ }
+ }
+
+ request.Imp = validImps
+
+ reqJSON, err := json.Marshal(request)
+ if err != nil {
+ errors = append(errors, err)
+ return nil, errors
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+
+ return []*adapters.RequestData{{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: reqJSON,
+ Headers: headers,
+ }}, errors
+}
+
+// MakeBids unpacks the server's response into Bids.
+func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if response.StatusCode == http.StatusBadRequest {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+ }}
+ }
+
+ if response.StatusCode != http.StatusOK {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+ }}
+ }
+
+ var bidResp openrtb.BidResponse
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+
+ for _, sb := range bidResp.SeatBid {
+ for i := range sb.Bid {
+ bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &sb.Bid[i],
+ BidType: bidType,
+ })
+ }
+ }
+ return bidResponse, nil
+
+}
+
+// NewYieldoneBidder configure bidder endpoint
+func NewYieldoneBidder(endpoint string) *YieldoneAdapter {
+ return &YieldoneAdapter{
+ endpoint: endpoint,
+ }
+}
+
+func preprocess(imp *openrtb.Imp) error {
+
+ var ext adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &ext); err != nil {
+ return err
+ }
+ var impressionExt openrtb_ext.ExtImpYieldone
+ if err := json.Unmarshal(ext.Bidder, &impressionExt); err != nil {
+ return err
+ }
+
+ if imp.Banner != nil {
+ bannerCopy := *imp.Banner
+ if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 {
+ firstFormat := bannerCopy.Format[0]
+ bannerCopy.W = &(firstFormat.W)
+ bannerCopy.H = &(firstFormat.H)
+ }
+ imp.Banner = &bannerCopy
+ }
+
+ return nil
+}
+
+func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) {
+ for _, imp := range imps {
+ if imp.ID == impID {
+ if imp.Banner != nil {
+ return openrtb_ext.BidTypeBanner, nil
+ }
+
+ if imp.Video != nil {
+ return openrtb_ext.BidTypeVideo, nil
+ }
+
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID),
+ }
+ }
+ }
+
+ // This shouldnt happen. Lets handle it just incase by returning an error.
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID),
+ }
+}
diff --git a/adapters/yieldone/yieldone_test.go b/adapters/yieldone/yieldone_test.go
new file mode 100644
index 00000000000..c61925411d9
--- /dev/null
+++ b/adapters/yieldone/yieldone_test.go
@@ -0,0 +1,11 @@
+package yieldone
+
+import (
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "yieldonetest", NewYieldoneBidder("http://localhost/prebid"))
+}
diff --git a/adapters/yieldone/yieldonetest/exemplary/simple-banner.json b/adapters/yieldone/yieldonetest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..f84476f1e86
--- /dev/null
+++ b/adapters/yieldone/yieldonetest/exemplary/simple-banner.json
@@ -0,0 +1,89 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "http://localhost/prebid",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "seat": "yieldone",
+ "bid": [{
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "12345678",
+ "adm": "some-test-ad",
+ "cid": "987",
+ "crid": "12345678",
+ "h": 250,
+ "w": 300
+ }]
+ }],
+ "cur": "JPY"
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "JPY",
+ "bids": [{
+ "bid": {
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "12345678",
+ "cid": "987",
+ "crid": "12345678",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
diff --git a/adapters/yieldone/yieldonetest/exemplary/simple-video.json b/adapters/yieldone/yieldonetest/exemplary/simple-video.json
new file mode 100644
index 00000000000..dc313abede7
--- /dev/null
+++ b/adapters/yieldone/yieldonetest/exemplary/simple-video.json
@@ -0,0 +1,87 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "41993"
+ }
+ }
+ }]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "http://localhost/prebid",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "41993"
+ }
+ }
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "seat": "yieldone",
+ "bid": [{
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "12345678",
+ "adm": "some-test-ad-vast",
+ "cid": "987",
+ "crid": "12345678",
+ "h": 250,
+ "w": 300
+ }]
+ }],
+ "cur": "JPY"
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "JPY",
+ "bids": [{
+ "bid": {
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "some-test-ad-vast",
+ "adid": "12345678",
+ "cid": "987",
+ "crid": "12345678",
+ "w": 300,
+ "h": 250
+ },
+ "type": "video"
+ }]
+ }]
+}
diff --git a/adapters/yieldone/yieldonetest/params/race/banner.json b/adapters/yieldone/yieldonetest/params/race/banner.json
new file mode 100644
index 00000000000..c88180845eb
--- /dev/null
+++ b/adapters/yieldone/yieldonetest/params/race/banner.json
@@ -0,0 +1,4 @@
+{
+ "placementId": "36891"
+}
+
diff --git a/adapters/yieldone/yieldonetest/supplemental/bad_response.json b/adapters/yieldone/yieldonetest/supplemental/bad_response.json
new file mode 100644
index 00000000000..fa993a2fff5
--- /dev/null
+++ b/adapters/yieldone/yieldonetest/supplemental/bad_response.json
@@ -0,0 +1,65 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://localhost/prebid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": "{\"id\"data.lost"
+ }
+ }
+ ],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/yieldone/yieldonetest/supplemental/status_204.json b/adapters/yieldone/yieldonetest/supplemental/status_204.json
new file mode 100644
index 00000000000..b1c9304a35a
--- /dev/null
+++ b/adapters/yieldone/yieldonetest/supplemental/status_204.json
@@ -0,0 +1,60 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://localhost/prebid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+
+ "expectedBidResponses": []
+}
diff --git a/adapters/yieldone/yieldonetest/supplemental/status_400.json b/adapters/yieldone/yieldonetest/supplemental/status_400.json
new file mode 100644
index 00000000000..1cb172bb371
--- /dev/null
+++ b/adapters/yieldone/yieldonetest/supplemental/status_400.json
@@ -0,0 +1,65 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://localhost/prebid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 400,
+ "body": {}
+ }
+ }
+ ],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/yieldone/yieldonetest/supplemental/status_418.json b/adapters/yieldone/yieldonetest/supplemental/status_418.json
new file mode 100644
index 00000000000..30cc16adde5
--- /dev/null
+++ b/adapters/yieldone/yieldonetest/supplemental/status_418.json
@@ -0,0 +1,65 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://localhost/prebid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "36891"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 418,
+ "body": {}
+ }
+ }
+ ],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 418. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/zeroclickfraud/usersync.go b/adapters/zeroclickfraud/usersync.go
new file mode 100644
index 00000000000..a5435335ab8
--- /dev/null
+++ b/adapters/zeroclickfraud/usersync.go
@@ -0,0 +1,12 @@
+package zeroclickfraud
+
+import (
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/usersync"
+)
+
+func NewZeroClickFraudSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("zeroclickfraud", 0, temp, adapters.SyncTypeIframe)
+}
diff --git a/adapters/zeroclickfraud/usersync_test.go b/adapters/zeroclickfraud/usersync_test.go
new file mode 100644
index 00000000000..e6a54fd9a5c
--- /dev/null
+++ b/adapters/zeroclickfraud/usersync_test.go
@@ -0,0 +1,34 @@
+package zeroclickfraud
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa"
+ "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestZeroClickFraudSyncer(t *testing.T) {
+ syncURL := "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewZeroClickFraudSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "1",
+ Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw",
+ },
+ CCPA: ccpa.Policy{
+ Value: "1NYN",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "https://s.0cf.io/sync?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24%7Buid%7D", syncInfo.URL)
+ assert.Equal(t, "iframe", syncInfo.Type)
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go
new file mode 100644
index 00000000000..560f4456580
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraud.go
@@ -0,0 +1,187 @@
+package zeroclickfraud
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/PubMatic-OpenWrap/openrtb"
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters"
+ "github.com/PubMatic-OpenWrap/prebid-server/errortypes"
+ "github.com/PubMatic-OpenWrap/prebid-server/macros"
+ "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
+ "github.com/golang/glog"
+ "net/http"
+ "strconv"
+ "text/template"
+)
+
+type ZeroClickFraudAdapter struct {
+ EndpointTemplate template.Template
+}
+
+func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+
+ errs := make([]error, 0, len(request.Imp))
+ headers := http.Header{
+ "Content-Type": {"application/json"},
+ "Accept": {"application/json"},
+ }
+
+ // Pull the host and source ID info from the bidder params.
+ reqImps, err := splitImpressions(request.Imp)
+
+ if err != nil {
+ errs = append(errs, err)
+ }
+
+ requests := []*adapters.RequestData{}
+
+ for reqExt, reqImp := range reqImps {
+ request.Imp = reqImp
+ reqJson, err := json.Marshal(request)
+
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ urlParams := macros.EndpointTemplateParams{Host: reqExt.Host, SourceId: strconv.Itoa(reqExt.SourceId)}
+ url, err := macros.ResolveMacros(a.EndpointTemplate, urlParams)
+
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ request := adapters.RequestData{
+ Method: "POST",
+ Uri: url,
+ Body: reqJson,
+ Headers: headers}
+
+ requests = append(requests, &request)
+ }
+
+ return requests, errs
+}
+
+/*
+internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests)
+*/
+func (a *ZeroClickFraudAdapter) MakeBids(
+ internalRequest *openrtb.BidRequest,
+ externalRequest *adapters.RequestData,
+ response *adapters.ResponseData,
+) (*adapters.BidderResponse, []error) {
+
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if response.StatusCode == http.StatusBadRequest {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("ERR, bad input %d", response.StatusCode),
+ }}
+ } else if response.StatusCode != http.StatusOK {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("ERR, response with status %d", response.StatusCode),
+ }}
+ }
+
+ var bidResp openrtb.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse := adapters.NewBidderResponse()
+ bidResponse.Currency = bidResp.Cur
+
+ for _, seatBid := range bidResp.SeatBid {
+ for i := 0; i < len(seatBid.Bid); i++ {
+ bid := seatBid.Bid[i]
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: getMediaType(bid.ImpID, internalRequest.Imp),
+ })
+ }
+ }
+
+ return bidResponse, nil
+}
+
+func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp, error) {
+
+ var m = make(map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp)
+
+ for _, imp := range imps {
+ bidderParams, err := getBidderParams(&imp)
+ if err != nil {
+ return nil, err
+ }
+
+ m[*bidderParams] = append(m[*bidderParams], imp)
+ }
+
+ return m, nil
+}
+
+func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("Missing bidder ext: %s", err.Error()),
+ }
+ }
+ var zeroclickfraudExt openrtb_ext.ExtImpZeroClickFraud
+ if err := json.Unmarshal(bidderExt.Bidder, &zeroclickfraudExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("Cannot Resolve host or sourceId: %s", err.Error()),
+ }
+ }
+
+ if zeroclickfraudExt.SourceId < 1 {
+ return nil, &errortypes.BadInput{
+ Message: "Invalid/Missing SourceId",
+ }
+ }
+
+ if len(zeroclickfraudExt.Host) < 1 {
+ return nil, &errortypes.BadInput{
+ Message: "Invalid/Missing Host",
+ }
+ }
+
+ return &zeroclickfraudExt, nil
+}
+
+func getMediaType(impID string, imps []openrtb.Imp) openrtb_ext.BidType {
+
+ bidType := openrtb_ext.BidTypeBanner
+
+ for _, imp := range imps {
+ if imp.ID == impID {
+ if imp.Video != nil {
+ bidType = openrtb_ext.BidTypeVideo
+ break
+ } else if imp.Native != nil {
+ bidType = openrtb_ext.BidTypeNative
+ break
+ } else {
+ bidType = openrtb_ext.BidTypeBanner
+ break
+ }
+ }
+ }
+
+ return bidType
+}
+
+func NewZeroClickFraudBidder(endpoint string) *ZeroClickFraudAdapter {
+ template, err := template.New("endpointTemplate").Parse(endpoint)
+ if err != nil {
+ glog.Fatal("Unable to parse endpoint url template")
+ return nil
+ }
+
+ return &ZeroClickFraudAdapter{EndpointTemplate: *template}
+}
diff --git a/adapters/zeroclickfraud/zeroclickfraud_test.go b/adapters/zeroclickfraud/zeroclickfraud_test.go
new file mode 100644
index 00000000000..a72bfdf8c80
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraud_test.go
@@ -0,0 +1,11 @@
+package zeroclickfraud
+
+import (
+ "testing"
+
+ "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "zeroclickfraudtest", NewZeroClickFraudBidder("http://{{.Host}}/openrtb2?sid={{.SourceId}}"))
+}
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json
new file mode 100644
index 00000000000..70bfb9645c8
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json
@@ -0,0 +1,160 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ },{
+ "id": "some-impression-id2",
+ "banner":
+ {
+ "format": [{
+ "w": 300,
+ "h": 600
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://q.0cf.io/openrtb2?sid=906295",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ },{
+ "id": "some-impression-id2",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 600
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 200,
+ "body":
+ {
+ "id": "some-request-id",
+ "bidid": "183975330-5-29038-2",
+ "seatbid": [
+ {
+ "seat": "906295",
+ "bid": [
+ {
+ "id": "2181314349",
+ "impid": "some-impression-id",
+ "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.zeroclickfraud.com ",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "w": 300,
+ "h": 250
+ }]
+ }],
+ "cur": "USD",
+ "ext":
+ {}
+ }
+ }
+ }],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid":
+ {
+ "id": "2181314349",
+ "impid": "some-impression-id",
+ "adm": "Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.zeroclickfraud.com ",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json
new file mode 100644
index 00000000000..dcf9064f29d
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json
@@ -0,0 +1,123 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native":
+ {
+ "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":500}},{\"id\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":2,\"data\":{\"type\":1,\"len\":200}},{\"id\":3,\"data\":{\"type\":2,\"len\":15000}},{\"id\":4,\"data\":{\"type\":6,\"len\":40}}]}",
+ "ver": "1.1"
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "user":
+ {
+ "buyeruid": "4610943261"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpcalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://q.0cf.io/openrtb2?sid=906295",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native":
+ {
+ "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":500}},{\"id\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":2,\"data\":{\"type\":1,\"len\":200}},{\"id\":3,\"data\":{\"type\":2,\"len\":15000}},{\"id\":4,\"data\":{\"type\":6,\"len\":40}}]}",
+ "ver": "1.1"
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "user":
+ {
+ "buyeruid": "4610943261"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status":200,
+ "body": {
+ "id": "some-request-id",
+ "bidid": "183975330-3-29038-2",
+ "seatbid": [
+ {
+ "seat": "906295",
+ "bid": [
+ {
+ "id": "2181314346",
+ "impid": "some-impression-id",
+ "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{ \"id\":0,\"required\":1,\"title\":{\"text\":\"Datablocks Inc.\"}},{ \"id\":3,\"required\":0,\"data\":{\"value\":\"Datablocks provides world class \\\"Software as a Service\\\" (SaaS) solutions to its clients.\"}}],\"link\":{\"url\":\"https://t.0cf.io/c/267237/?fcid=43154325321\"},\"imptrackers\":[\"https://t.0cf.io/i/267237/?fcid=43154325321&pixel=1\"],\"jstracker\":[]}}",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "ext": {}
+ }]
+ }],
+ "cur": "USD",
+ "ext": {}
+ }
+ }
+ }],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "2181314346",
+ "impid": "some-impression-id",
+ "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{ \"id\":0,\"required\":1,\"title\":{\"text\":\"Datablocks Inc.\"}},{ \"id\":3,\"required\":0,\"data\":{\"value\":\"Datablocks provides world class \\\"Software as a Service\\\" (SaaS) solutions to its clients.\"}}],\"link\":{\"url\":\"https://t.0cf.io/c/267237/?fcid=43154325321\"},\"imptrackers\":[\"https://t.0cf.io/i/267237/?fcid=43154325321&pixel=1\"],\"jstracker\":[]}}",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "ext": {}
+ },
+ "type":"native"
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..1d5ee3b3a52
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json
@@ -0,0 +1,133 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://q.0cf.io/openrtb2?sid=906295",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 200,
+ "body":
+ {
+ "id": "some-request-id",
+ "bidid": "183975330-5-29038-2",
+ "seatbid": [
+ {
+ "seat": "906295",
+ "bid": [
+ {
+ "id": "2181314349",
+ "impid": "some-impression-id",
+ "adm": "Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.zeroclickfraud.com.net ",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "w": 300,
+ "h": 250
+ }]
+ }],
+ "cur": "USD",
+ "ext":
+ {}
+ }
+ }
+ }],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid":
+ {
+ "id": "2181314349",
+ "impid": "some-impression-id",
+ "adm": "Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.zeroclickfraud.com.net ",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json
new file mode 100644
index 00000000000..949e74602dd
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json
@@ -0,0 +1,138 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video":
+ {
+ "mimes":[
+ "video/x-flv"
+ ],
+ "w": 500,
+ "h": 400,
+ "minduration": 30
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://q.0cf.io/openrtb2?sid=906295",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video":
+ {
+ "mimes":[
+ "video/x-flv"
+ ],
+ "w": 500,
+ "h": 400,
+ "minduration": 30
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 200,
+ "body":
+ {
+ "id": "some-request-id",
+ "bidid": "183975330-4-29038-2",
+ "seatbid": [
+ {
+ "seat": "906295",
+ "bid": [
+ {
+ "id": "2181314347",
+ "impid": "some-impression-id",
+ "nurl": "https://t.0cf.io/wm/267237/?fcid=2181314347",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906729",
+ "w": 500,
+ "h": 400,
+ "ext":
+ {
+ "type": "CPM"
+ }
+ }]
+ }],
+ "cur": "USD",
+ "ext":
+ {}
+ }
+ }
+ }],
+
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid":
+ {
+ "id": "2181314347",
+ "impid": "some-impression-id",
+ "price": 13.37,
+ "nurl": "https://t.0cf.io/wm/267237/?fcid=2181314347",
+ "adid": "906297",
+ "cid": "906293",
+ "crid": "906729",
+ "w": 500,
+ "h": 400,
+ "ext":
+ {
+ "type": "CPM"
+ }
+ },
+ "type": "video"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json
new file mode 100644
index 00000000000..cff0af83143
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json
@@ -0,0 +1,4 @@
+{
+ "sourceId": 906295,
+ "host": "q.0cf.io"
+}
\ No newline at end of file
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json
new file mode 100644
index 00000000000..cff0af83143
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json
@@ -0,0 +1,4 @@
+{
+ "sourceId": 906295,
+ "host": "q.0cf.io"
+}
\ No newline at end of file
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json
new file mode 100644
index 00000000000..cff0af83143
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json
@@ -0,0 +1,4 @@
+{
+ "sourceId": 906295,
+ "host": "q.0cf.io"
+}
\ No newline at end of file
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-host.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-host.json
new file mode 100644
index 00000000000..cee5efbe760
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-host.json
@@ -0,0 +1,33 @@
+{
+ "mockBidRequest": {
+ "id": "bad-host-test",
+ "imp": [
+ {
+ "id": "bad-host-test-imp",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "",
+ "sourceId": 123
+ }
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Invalid/Missing Host",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json
new file mode 100644
index 00000000000..84d6bd9d889
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json
@@ -0,0 +1,88 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://q.0cf.io/openrtb2?sid=123",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 200,
+ "body":"foobar"
+ }
+ }],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json
new file mode 100644
index 00000000000..fdea4f109a7
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json
@@ -0,0 +1,88 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://q.0cf.io/openrtb2?sid=123",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 500,
+ "body": {}
+ }
+ }],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "ERR, response with status 500",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-sourceId.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-sourceId.json
new file mode 100644
index 00000000000..4d86c32cd58
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-sourceId.json
@@ -0,0 +1,35 @@
+{
+ "mockBidRequest": {
+ "id": "bad-sourceId-test",
+ "imp": [
+ {
+ "id": "bad-sourceId-test-imp",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 0
+ }
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Invalid/Missing SourceId",
+ "comparison": "literal"
+ }
+ ]
+
+
+}
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json
new file mode 100644
index 00000000000..68d29e880b9
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json
@@ -0,0 +1,27 @@
+{
+ "mockBidRequest": {
+ "id": "missing-extbid-test",
+ "imp": [
+ {
+ "id": "missing-extbid-test",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Missing bidder ext: unexpected end of JSON input",
+ "comparison": "literal"
+ }
+ ]
+
+
+}
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json
new file mode 100644
index 00000000000..d272cd5347c
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json
@@ -0,0 +1,30 @@
+{
+ "mockBidRequest": {
+ "id": "missing-extbid-test",
+ "imp": [
+ {
+ "id": "missing-extbid-test",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "sourceId":54326
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Cannot Resolve host or sourceId: unexpected end of JSON input",
+ "comparison": "literal"
+ }
+ ]
+
+
+}
diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json
new file mode 100644
index 00000000000..3a36d6e04b2
--- /dev/null
+++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json
@@ -0,0 +1,82 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://q.0cf.io/openrtb2?sid=123",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "q.0cf.io",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 204
+ }
+ }],
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go
index e9847a3902e..3ae1fa3f82d 100644
--- a/analytics/config/config_test.go
+++ b/analytics/config/config_test.go
@@ -22,7 +22,7 @@ func TestSampleModule(t *testing.T) {
Response: &openrtb.BidResponse{},
})
if count != 1 {
- t.Errorf("PBSAnalyticsModule failed at LogAuctionObejct")
+ t.Errorf("PBSAnalyticsModule failed at LogAuctionObject")
}
am.LogSetUIDObject(&analytics.SetUIDObject{
@@ -33,12 +33,12 @@ func TestSampleModule(t *testing.T) {
Success: true,
})
if count != 2 {
- t.Errorf("PBSAnalyticsModule failed at LogSetUIDObejct")
+ t.Errorf("PBSAnalyticsModule failed at LogSetUIDObject")
}
am.LogCookieSyncObject(&analytics.CookieSyncObject{})
if count != 3 {
- t.Errorf("PBSAnalyticsModule failed at LogCookieSyncObejct")
+ t.Errorf("PBSAnalyticsModule failed at LogCookieSyncObject")
}
am.LogAmpObject(&analytics.AmpObject{})
diff --git a/config/config.go b/config/config.go
old mode 100644
new mode 100755
index bac9ee17e6f..e27358b3d44
--- a/config/config.go
+++ b/config/config.go
@@ -23,6 +23,7 @@ type Configuration struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Client HTTPClient `mapstructure:"http_client"`
+ CacheClient HTTPClient `mapstructure:"http_client_cache"`
AdminPort int `mapstructure:"admin_port"`
EnableGzip bool `mapstructure:"enable_gzip"`
// StatusResponse is the string which will be returned by the /status endpoint when things are OK.
@@ -48,6 +49,7 @@ type Configuration struct {
AMPTimeoutAdjustment int64 `mapstructure:"amp_timeout_adjustment_ms"`
GDPR GDPR `mapstructure:"gdpr"`
CCPA CCPA `mapstructure:"ccpa"`
+ LMT LMT `mapstructure:"lmt"`
CurrencyConverter CurrencyConverter `mapstructure:"currency_converter"`
DefReqConfig DefReqConfig `mapstructure:"default_request"`
@@ -63,6 +65,10 @@ type Configuration struct {
AccountRequired bool `mapstructure:"account_required"`
// Local private file containing SSL certificates
PemCertsFile string `mapstructure:"certificates_file"`
+ // Custom headers to handle request timeouts from queueing infrastructure
+ RequestTimeoutHeaders RequestTimeoutHeaders `mapstructure:"request_timeout_headers"`
+ // Debug/logging flags go here
+ Debug Debug `mapstructure:"debug"`
}
const MIN_COOKIE_SIZE_BYTES = 500
@@ -102,6 +108,7 @@ func (cfg *Configuration) validate() configErrors {
errs = cfg.GDPR.validate(errs)
errs = cfg.CurrencyConverter.validate(errs)
errs = validateAdapters(cfg.Adapters, errs)
+ errs = cfg.Debug.validate(errs)
return errs
}
@@ -134,12 +141,21 @@ func (cfg *AuctionTimeouts) LimitAuctionTimeout(requested time.Duration) time.Du
return requested
}
+// Privacy is a grouping of privacy related configs to assist in dependency injection.
+type Privacy struct {
+ CCPA CCPA
+ GDPR GDPR
+ LMT LMT
+}
+
type GDPR struct {
HostVendorID int `mapstructure:"host_vendor_id"`
UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"`
Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"`
NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"`
NonStandardPublisherMap map[string]int
+ TCF2 TCF2 `mapstructure:"tcf2"`
+ AMPException bool `mapstructure:"amp_exception"`
}
func (cfg *GDPR) validate(errs configErrors) configErrors {
@@ -162,10 +178,34 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration {
return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond
}
+// TCF2 defines the TCF2 specific configurations for GDPR
+type TCF2 struct {
+ Enabled bool `mapstructure:"enabled"`
+ Purpose1 PurposeDetail `mapstructure:"purpose1"`
+ Purpose2 PurposeDetail `mapstructure:"purpose2"`
+ Purpose7 PurposeDetail `mapstructure:"purpose7"`
+ SpecialPurpose1 PurposeDetail `mapstructure:"special_purpose1"`
+ PurposeOneTreatment PurposeOneTreatement `mapstructure:"purpose_one_treatement"`
+}
+
+// Making a purpose struct so purpose specific details can be added later.
+type PurposeDetail struct {
+ Enabled bool `mapstructure:"enabled"`
+}
+
+type PurposeOneTreatement struct {
+ Enabled bool `mapstructure:"enabled"`
+ AccessAllowed bool `mapstructure:"access_allowed"`
+}
+
type CCPA struct {
Enforce bool `mapstructure:"enforce"`
}
+type LMT struct {
+ Enforce bool `mapstructure:"enforce"`
+}
+
type Analytics struct {
File FileLogs `mapstructure:"file"`
}
@@ -199,6 +239,11 @@ type HostCookie struct {
TTL int64 `mapstructure:"ttl_days"`
}
+type RequestTimeoutHeaders struct {
+ RequestTimeInQueue string `mapstructure:"request_time_in_queue"`
+ RequestTimeoutInQueue string `mapstructure:"request_timeout_in_queue"`
+}
+
func (cfg *HostCookie) TTLDuration() time.Duration {
return time.Duration(cfg.TTL) * time.Hour * 24
}
@@ -206,6 +251,7 @@ func (cfg *HostCookie) TTLDuration() time.Duration {
const (
dummyHost string = "dummyhost.com"
dummyPublisherID string = "12"
+ dummyAccountID string = "some_account"
dummyGDPR string = "0"
dummyGDPRConsent string = "someGDPRConsentString"
dummyCCPA string = "1NYN"
@@ -214,7 +260,7 @@ const (
type Adapter struct {
Endpoint string `mapstructure:"endpoint"` // Required
// UserSyncURL is the URL returned by /cookie_sync for this Bidder. It is _usually_ optional.
- // If not defined, sensible defaults will be derved based on the config.external_url.
+ // If not defined, sensible defaults will be derived based on the config.external_url.
// Note that some Bidders don't have sensible defaults, because their APIs require an ID that will vary
// from one PBS host to another.
//
@@ -256,7 +302,7 @@ func validateAdapterEndpoint(endpoint string, adapterName string, errs configErr
return append(errs, fmt.Errorf("Invalid endpoint template: %s for adapter: %s. %v", endpoint, adapterName, err))
}
// Resolve macros (if any) in the endpoint URL
- resolvedEndpoint, err := macros.ResolveMacros(*endpointTemplate, macros.EndpointTemplateParams{Host: dummyHost, PublisherID: dummyPublisherID})
+ resolvedEndpoint, err := macros.ResolveMacros(*endpointTemplate, macros.EndpointTemplateParams{Host: dummyHost, PublisherID: dummyPublisherID, AccountID: dummyAccountID})
if err != nil {
return append(errs, fmt.Errorf("Unable to resolve endpoint: %s for adapter: %s. %v", endpoint, adapterName, err))
}
@@ -420,6 +466,30 @@ type DefReqFiles struct {
FileName string `mapstructure:"name"`
}
+type Debug struct {
+ TimeoutNotification TimeoutNotification `mapstructure:"timeout_notification"`
+}
+
+func (cfg *Debug) validate(errs configErrors) configErrors {
+ return cfg.TimeoutNotification.validate(errs)
+}
+
+type TimeoutNotification struct {
+ // Log timeout notifications in the application log
+ Log bool `mapstructure:"log"`
+ // Fraction of notifications to log
+ SamplingRate float32 `mapstructure:"sampling_rate"`
+ // Only log failures
+ FailOnly bool `mapstructure:"fail_only"`
+}
+
+func (cfg *TimeoutNotification) validate(errs configErrors) configErrors {
+ if cfg.SamplingRate < 0.0 || cfg.SamplingRate > 1.0 {
+ errs = append(errs, fmt.Errorf("debug.timeout_notification.sampling_rate must be positive and not greater than 1.0. Got %f", cfg.SamplingRate))
+ }
+ return errs
+}
+
// New uses viper to get our server configurations.
func New(v *viper.Viper) (*Configuration, error) {
var c Configuration
@@ -487,20 +557,29 @@ func (cfg *Configuration) setDerivedDefaults() {
syncRedirectEndpoint := url.QueryEscape(cfg.ExternalURL + SETUID_ENDPOINT)
setDefaultUsersync(cfg.Adapters, openrtb_ext.Bidder33Across, "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru="+syncRedirectEndpoint+"bidder%3D33across%26uid%3D33XUSERID33X&id=zzz000000000002zzz")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdform, "https://cm.adform.net/cookie?redirect_url="+syncRedirectEndpoint+"bidder%3Dadform%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ // openrtb_ext.BidderAdgeneration doesn't have a good default.
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernel, "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+syncRedirectEndpoint+"bidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernelAdn, "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+syncRedirectEndpoint+"bidder%3DadkernelAdn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdpone, "https://usersync.adpone.com/csync?redir="+syncRedirectEndpoint+"bidder%3Dadpone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D")
- setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&redir="+syncRedirectEndpoint+"bidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtarget, "https://sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Dadtarget%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+syncRedirectEndpoint+"bidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24")
+ // openrtb_ext.BidderAdOcean doesn't have a good default.
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+syncRedirectEndpoint+"bidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAJA, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Daja%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25s")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAppnexus, "https://ib.adnxs.com/getuid?"+syncRedirectEndpoint+"bidder%3Dadnxs%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAvocet, "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+syncRedirectEndpoint+"bidder%3Davocet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BUUID%7D%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+syncRedirectEndpoint+"bidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeintoo, "https://ib.beintoo.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+syncRedirectEndpoint+"bidder%3Dbeintoo%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+syncRedirectEndpoint+"bidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
- setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/prebid/match?rurl="+syncRedirectEndpoint+"bidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl="+syncRedirectEndpoint+"bidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+syncRedirectEndpoint+"bidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+syncRedirectEndpoint+"bidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDmx, "https://dmx.districtm.io/s/v1/img/s/10007?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+syncRedirectEndpoint+"bidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+syncRedirectEndpoint+"bidder%3Demx_digital%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+syncRedirectEndpoint+"bidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
- setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du=https%3A%2F%2Fads.us.e-planning.net%2Fgetuid%2F1%2F5a1ad71d2d53a0f5%3F"+syncRedirectEndpoint+"bidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du="+syncRedirectEndpoint+"bidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
// openrtb_ext.BidderFacebook doesn't have a good default.
// openrtb_ext.BidderGamma doesn't have a good default.
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGamoshi, "https://rtb.gamoshi.io/user_sync_prebid?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+syncRedirectEndpoint+"bidder%3Dgamoshi%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bgusr%5D")
@@ -510,27 +589,36 @@ func (cfg *Configuration) setDerivedDefaults() {
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum-sec.casalemedia.com/usermatchredir?s=186523&cb="+syncRedirectEndpoint+"bidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLifestreet, "https://ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl="+syncRedirectEndpoint+"bidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+syncRedirectEndpoint+"bidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+syncRedirectEndpoint+"bidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+syncRedirectEndpoint+"bidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+syncRedirectEndpoint+"bidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D")
- setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?r="+syncRedirectEndpoint+"bidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNanoInteractive, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+syncRedirectEndpoint+"bidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+syncRedirectEndpoint+"bidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+syncRedirectEndpoint+"bidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+syncRedirectEndpoint+"bidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPulsepoint, "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl="+syncRedirectEndpoint+"bidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderRhythmone, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D")
// openrtb_ext.BidderRTBHouse doesn't have a good default.
// openrtb_ext.BidderRubicon doesn't have a good default.
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSharethrough, "https://match.sharethrough.com/FGMrCMMc/v1?redirectUri="+syncRedirectEndpoint+"bidder%3Dsharethrough%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartRTB, "https://market-global.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&rr="+syncRedirectEndpoint+"bidder%253Dsmartrtb%2526gdpr%253D{{.GDPR}}%2526gdpr_consent%253D{{.GDPRConsent}}%2526uid%253D%257BXID%257D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSomoaudience, "https://publisher-east.mobileadtrading.com/usersync?ru="+syncRedirectEndpoint+"bidder%3Dsomoaudience%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSonobi, "https://sync.go.sonobi.com/us.gif?loc="+syncRedirectEndpoint+"bidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+syncRedirectEndpoint+"bidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSynacormedia, "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb="+syncRedirectEndpoint+"bidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D")
// openrtb_ext.BidderTappx doesn't have a good default.
- setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTelaria, "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir="+syncRedirectEndpoint+"%2Fsetuid%3Fbidder%3Dtelaria%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Btvid%5D")
- setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gpdr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
- setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/sync?gpdr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTelaria, "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir="+syncRedirectEndpoint+"bidder%3Dtelaria%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Btvid%5D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+syncRedirectEndpoint+"bidder%3Ducfunnel%26uid%3DSspCookieUserId")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+syncRedirectEndpoint+"bidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+syncRedirectEndpoint+"bidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+syncRedirectEndpoint+"bidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
// openrtb_ext.BidderVrtcal doesn't have a good default.
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldlab, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+syncRedirectEndpoint+"bidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+syncRedirectEndpoint+"bidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_cs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+syncRedirectEndpoint+"bidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+syncRedirectEndpoint+"bidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D")
}
func setDefaultUsersync(m map[string]Adapter, bidder openrtb_ext.BidderName, defaultValue string) {
@@ -583,6 +671,9 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("http_client.max_idle_connections", 400)
v.SetDefault("http_client.max_idle_connections_per_host", 10)
v.SetDefault("http_client.idle_connection_timeout_seconds", 60)
+ v.SetDefault("http_client_cache.max_idle_connections", 10)
+ v.SetDefault("http_client_cache.max_idle_connections_per_host", 2)
+ v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60)
// no metrics configured by default (metrics{host|database|username|password})
v.SetDefault("metrics.disabled_metrics.account_adapter_details", false)
v.SetDefault("metrics.influxdb.host", "")
@@ -662,38 +753,57 @@ func SetupViper(v *viper.Viper, filename string) {
// If you're using one of these, make sure you check out the documentation (https://github.com/PubMatic-OpenWrap/prebid-server/tree/master/docs/bidders)
// for them and specify all the parameters they need for them to work correctly.
v.SetDefault("adapters.audiencenetwork.disabled", true)
-
+ //v.SetDefault("adapters.rubicon.disabled", true)
v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb")
v.SetDefault("adapters.33across.partner_id", "")
+ v.SetDefault("adapters.dmx.endpoint", "https://dmx.districtm.io/b/v2")
+ v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction")
v.SetDefault("adapters.adform.endpoint", "http://adx.adform.net/adx")
+ v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1")
+ v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json")
v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}")
v.SetDefault("adapters.adkerneladn.endpoint", "http://{{.Host}}/rtbpub?account={{.PublisherID}}")
+ v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx")
+ v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}")
+ v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads")
v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server")
- v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction")
+ v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb")
+ v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb")
v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}")
+ v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4")
v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid")
v.SetDefault("adapters.appnexus.endpoint", "http://ib.adnxs.com/openrtb2") // Docs: https://wiki.appnexus.com/display/supply/Incoming+Bid+Request+from+SSPs
v.SetDefault("adapters.appnexus.platform_id", "5")
+ v.SetDefault("adapters.avocet.disabled", true)
v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display")
+ v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}")
+ v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um")
v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs")
v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2")
v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/s2s/header/24")
+ v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx")
v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com")
v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb")
- v.SetDefault("adapters.eplanning.endpoint", "http://ads.us.e-planning.net/hb/1")
+ v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1")
v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/")
v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io")
v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid")
v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid")
v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs")
v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932")
+ v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server")
v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid")
v.SetDefault("adapters.lifestreet.endpoint", "https://prebid.s2s.lfstmedia.com/adrequest")
v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2")
+ v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}")
v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet")
v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/")
+ v.SetDefault("adapters.mobilefuse.endpoint", "http://mfx-us-east.mobilefuse.com/openrtb?pub_id={{.PublisherID}}")
+ v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs")
+ v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}")
v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid")
+ v.SetDefault("adapters.orbidder.endpoint", "https://orbidder.otto.de/openrtb2")
v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server")
v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request")
v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s")
@@ -701,6 +811,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids")
v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json")
v.SetDefault("adapters.sharethrough.endpoint", "http://btlr.sharethrough.com/FGMrCMMc/v1")
+ v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}")
v.SetDefault("adapters.somoaudience.endpoint", "http://publisher-east.mobileadtrading.com/rtb/bid")
v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af")
v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server")
@@ -710,12 +821,18 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.telaria.endpoint", "https://ads.tremorhub.com/ad/rtb/prebid")
v.SetDefault("adapters.triplelift_native.disabled", true)
v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}")
- v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?supplier_id=20")
+ v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?sra=1&supplier_id=20")
+ v.SetDefault("adapters.ucfunnel.endpoint", "http://apac-hk-adx.aralego.com/prebid")
v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2")
+ v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint")
v.SetDefault("adapters.verizonmedia.disabled", true)
v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard")
v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804")
+ v.SetDefault("adapters.yeahmobi.endpoint", "https://{{.Host}}/prebid/bid")
+ v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/")
v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server")
+ v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp")
+ v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
v.SetDefault("max_request_size", 1024*256)
v.SetDefault("analytics.file.filename", "")
@@ -725,7 +842,17 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0)
v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0)
v.SetDefault("gdpr.non_standard_publishers", []string{""})
+ v.SetDefault("gdpr.tcf2.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose1.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose2.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose4.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose7.enabled", true)
+ v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose_one_treatement.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true)
+ v.SetDefault("gdpr.amp_exception", false)
v.SetDefault("ccpa.enforce", false)
+ v.SetDefault("lmt.enforce", true)
v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json")
v.SetDefault("currency_converter.fetch_interval_seconds", 1800) // fetch currency rates every 30 minutes
v.SetDefault("default_request.type", "")
@@ -736,6 +863,13 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("account_required", false)
v.SetDefault("certificates_file", "")
+ v.SetDefault("request_timeout_headers.request_time_in_queue", "")
+ v.SetDefault("request_timeout_headers.request_timeout_in_queue", "")
+
+ v.SetDefault("debug.timeout_notification.log", false)
+ v.SetDefault("debug.timeout_notification.sampling_rate", 0.0)
+ v.SetDefault("debug.timeout_notification.fail_only", false)
+
// Set environment variable support:
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.SetEnvPrefix("PBS")
diff --git a/config/config_test.go b/config/config_test.go
index 87511795a56..7853bbed1af 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -43,6 +43,8 @@ gdpr:
non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"]
ccpa:
enforce: true
+lmt:
+ enforce: true
host_cookie:
cookie_name: userid
family: prebid
@@ -68,6 +70,10 @@ http_client:
max_idle_connections: 500
max_idle_connections_per_host: 20
idle_connection_timeout_seconds: 30
+http_client_cache:
+ max_idle_connections: 1
+ max_idle_connections_per_host: 2
+ idle_connection_timeout_seconds: 3
currency_converter:
fetch_url: https://currency.prebid.org
fetch_interval_seconds: 1800
@@ -214,6 +220,9 @@ func TestFullConfig(t *testing.T) {
cmpInts(t, "http_client.max_idle_connections", cfg.Client.MaxIdleConns, 500)
cmpInts(t, "http_client.max_idle_connections_per_host", cfg.Client.MaxIdleConnsPerHost, 20)
cmpInts(t, "http_client.idle_connection_timeout_seconds", cfg.Client.IdleConnTimeout, 30)
+ cmpInts(t, "http_client_cache.max_idle_connections", cfg.CacheClient.MaxIdleConns, 1)
+ cmpInts(t, "http_client_cache.max_idle_connections_per_host", cfg.CacheClient.MaxIdleConnsPerHost, 2)
+ cmpInts(t, "http_client_cache.idle_connection_timeout_seconds", cfg.CacheClient.IdleConnTimeout, 3)
cmpInts(t, "gdpr.host_vendor_id", cfg.GDPR.HostVendorID, 15)
cmpBools(t, "gdpr.usersync_if_ambiguous", cfg.GDPR.UsersyncIfAmbiguous, true)
@@ -233,6 +242,7 @@ func TestFullConfig(t *testing.T) {
cmpBools(t, "cfg.GDPR.NonStandardPublisherMap", found, false)
cmpBools(t, "ccpa.enforce", cfg.CCPA.Enforce, true)
+ cmpBools(t, "lmt.enforce", cfg.LMT.Enforce, true)
//Assert the NonStandardPublishers was correctly unmarshalled
cmpStrings(t, "blacklisted_apps", cfg.BlacklistedApps[0], "spamAppID")
@@ -263,6 +273,8 @@ func TestFullConfig(t *testing.T) {
cmpStrings(t, "adapters.audiencenetwork.usersync_url", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].UserSyncURL, "http://facebook.com/ortb/prebid-s2s")
cmpStrings(t, "adapters.audiencenetwork.platform_id", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].PlatformID, "abcdefgh1234")
cmpStrings(t, "adapters.audiencenetwork.app_secret", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].AppSecret, "987abc")
+ cmpStrings(t, "adapters.beachfront.endpoint", cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, "https://display.bfmio.com/prebid_display")
+ cmpStrings(t, "adapters.beachfront.extra_info", cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo, "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}")
cmpStrings(t, "adapters.ix.endpoint", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint, "http://ixtest.com/api")
cmpStrings(t, "adapters.rubicon.endpoint", cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, "http://rubitest.com/api")
cmpStrings(t, "adapters.rubicon.usersync_url", cfg.Adapters[string(openrtb_ext.BidderRubicon)].UserSyncURL, "http://pixel.rubiconproject.com/sync.php?p=prebid")
@@ -410,13 +422,21 @@ func TestCookieSizeError(t *testing.T) {
}
for i := range testCases {
if testCases[i].expectError {
- assert.Error(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCooki.MaxCookieSizeBytes less than MIN_COOKIE_SIZE_BYTES = %d and not equal to zero should return an error", MIN_COOKIE_SIZE_BYTES))
+ assert.Error(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCookie.MaxCookieSizeBytes less than MIN_COOKIE_SIZE_BYTES = %d and not equal to zero should return an error", MIN_COOKIE_SIZE_BYTES))
} else {
- assert.NoError(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCooki.MaxCookieSizeBytes greater than MIN_COOKIE_SIZE_BYTES = %d or equal to zero should not return an error", MIN_COOKIE_SIZE_BYTES))
+ assert.NoError(t, isValidCookieSize(testCases[i].cookieHost.MaxCookieSizeBytes), fmt.Sprintf("Configuration.HostCookie.MaxCookieSizeBytes greater than MIN_COOKIE_SIZE_BYTES = %d or equal to zero should not return an error", MIN_COOKIE_SIZE_BYTES))
}
}
}
+func TestValidateDebug(t *testing.T) {
+ cfg := newDefaultConfig(t)
+ cfg.Debug.TimeoutNotification.SamplingRate = 1.1
+
+ err := cfg.validate()
+ assert.NotNil(t, err, "cfg.debug.timeout_notification.sampling_rate should not be allowed to be greater than 1.0, but it was allowed")
+}
+
func newDefaultConfig(t *testing.T) *Configuration {
v := viper.New()
SetupViper(v, "")
diff --git a/config/stored_requests.go b/config/stored_requests.go
index 0d9e773205e..04e400f9b7c 100644
--- a/config/stored_requests.go
+++ b/config/stored_requests.go
@@ -402,7 +402,7 @@ func (cfg *PostgresUpdatePolling) validate(errs configErrors) configErrors {
return errs
}
-// MakeQuery builds a query which can fetch numReqs Stored Requetss and numImps Stored Imps.
+// MakeQuery builds a query which can fetch numReqs Stored Requests and numImps Stored Imps.
// See the docs on PostgresConfig.QueryTemplate for a description of how it works.
func (cfg *PostgresFetcherQueriesSlim) MakeQuery(numReqs int, numImps int) (query string) {
return resolve(cfg.QueryTemplate, numReqs, numImps)
diff --git a/config/util/loggers.go b/config/util/loggers.go
new file mode 100644
index 00000000000..88702e68763
--- /dev/null
+++ b/config/util/loggers.go
@@ -0,0 +1,24 @@
+package util
+
+import (
+ "math/rand"
+)
+
+type logMsg func(string, ...interface{})
+
+type randomGenerator func() float32
+
+// LogRandomSample will log a randam sample of the messages it is sent, based on the chance to log
+// chance = 1.0 => always log,
+// chance = 0.0 => never log
+func LogRandomSample(msg string, logger logMsg, chance float32) {
+ logRandomSampleImpl(msg, logger, chance, rand.Float32)
+}
+
+func logRandomSampleImpl(msg string, logger logMsg, chance float32, randGenerator randomGenerator) {
+ if chance < 1.0 && randGenerator() > chance {
+ // this is the chance we don't log anything
+ return
+ }
+ logger(msg)
+}
diff --git a/config/util/loggers_test.go b/config/util/loggers_test.go
new file mode 100644
index 00000000000..4bfab967ec4
--- /dev/null
+++ b/config/util/loggers_test.go
@@ -0,0 +1,32 @@
+package util
+
+import (
+ "bytes"
+ "fmt"
+ "math/rand"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestLogRandomSample(t *testing.T) {
+
+ const expected string = `This is test line 2
+This is test line 3
+`
+
+ myRand := rand.New(rand.NewSource(1337))
+ var buf bytes.Buffer
+
+ mylogger := func(msg string, args ...interface{}) {
+ buf.WriteString(fmt.Sprintf(fmt.Sprintln(msg), args...))
+ }
+
+ logRandomSampleImpl("This is test line 1", mylogger, 0.5, myRand.Float32)
+ logRandomSampleImpl("This is test line 2", mylogger, 0.5, myRand.Float32)
+ logRandomSampleImpl("This is test line 3", mylogger, 0.5, myRand.Float32)
+ logRandomSampleImpl("This is test line 4", mylogger, 0.5, myRand.Float32)
+ logRandomSampleImpl("This is test line 5", mylogger, 0.5, myRand.Float32)
+
+ assert.EqualValues(t, expected, buf.String())
+}
diff --git a/currencies/rate_converter.go b/currencies/rate_converter.go
index 63f09bd3c2e..6c6ed172652 100644
--- a/currencies/rate_converter.go
+++ b/currencies/rate_converter.go
@@ -172,11 +172,15 @@ func (rc *RateConverter) Rates() Conversions {
// GetInfo returns setup information about the converter
func (rc *RateConverter) GetInfo() ConverterInfo {
+ var rates *map[string]map[string]float64
+ if rc.Rates() != nil {
+ rates = rc.Rates().GetRates()
+ }
return converterInfo{
source: rc.syncSourceURL,
fetchingInterval: rc.fetchingInterval,
lastUpdated: rc.LastUpdated(),
- rates: rc.Rates().GetRates(),
+ rates: rates,
}
}
diff --git a/currencies/rate_converter_test.go b/currencies/rate_converter_test.go
index 63ccd035c0c..5c6b4821d8c 100644
--- a/currencies/rate_converter_test.go
+++ b/currencies/rate_converter_test.go
@@ -66,6 +66,7 @@ func TestFetch_Success(t *testing.T) {
rates := currencyConverter.Rates()
assert.NotNil(t, rates, "Rates() should not return nil")
assert.Equal(t, expectedRates, rates, "Rates() doesn't return expected rates")
+ assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
}
func TestFetch_Fail404(t *testing.T) {
@@ -92,6 +93,7 @@ func TestFetch_Fail404(t *testing.T) {
assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs))
assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil")
+ assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
}
func TestFetch_FailErrorHttpClient(t *testing.T) {
@@ -118,6 +120,7 @@ func TestFetch_FailErrorHttpClient(t *testing.T) {
assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs))
assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil")
+ assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
}
func TestFetch_FailBadSyncURL(t *testing.T) {
@@ -134,6 +137,7 @@ func TestFetch_FailBadSyncURL(t *testing.T) {
// Verify:
assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil")
+ assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
}
func TestFetch_FailBadJSON(t *testing.T) {
@@ -174,6 +178,7 @@ func TestFetch_FailBadJSON(t *testing.T) {
assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs))
assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil")
+ assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
}
func TestFetch_InvalidRemoteResponseContent(t *testing.T) {
@@ -201,6 +206,7 @@ func TestFetch_InvalidRemoteResponseContent(t *testing.T) {
assert.Equal(t, 1, len(calledURLs), "sync URL should have been called %d times but was %d", 1, len(calledURLs))
assert.Equal(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated() shouldn't return a time set")
assert.Nil(t, currencyConverter.Rates(), "Rates() should return nil")
+ assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
}
func TestInit(t *testing.T) {
@@ -264,6 +270,7 @@ func TestInit(t *testing.T) {
assert.NotEqual(t, currencyConverter.LastUpdated(), (time.Time{}), "LastUpdated should be set")
rates := currencyConverter.Rates()
assert.Equal(t, expectedRates, rates, "Conversions.Rates weren't the expected ones")
+ assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
if ticksCount == expectedTicks {
currencyConverter.StopPeriodicFetching()
@@ -361,6 +368,7 @@ func TestInitWithZeroDuration(t *testing.T) {
assert.Equal(t, (time.Time{}), currencyConverter.LastUpdated(), "LastUpdated() shouldn't be set")
_, ok := currencyConverter.Rates().(*currencies.ConstantRates)
assert.True(t, ok, "Rates should be type of `currencies.ConstantRates`")
+ assert.NotNil(t, currencyConverter.GetInfo(), "GetInfo() should not return nil")
}
func TestRates(t *testing.T) {
diff --git a/docs/bidders/adtarget.md b/docs/bidders/adtarget.md
new file mode 100644
index 00000000000..b658a728a2b
--- /dev/null
+++ b/docs/bidders/adtarget.md
@@ -0,0 +1,5 @@
+# Adtarget bidder
+
+To use the Adtarget bidder you will need an aid from an exchange account on [https://console.adtarget.com.tr](adtarget.com.tr).
+
+For further information, please contact kamil@adtarget.com.tr
\ No newline at end of file
diff --git a/docs/bidders/appnexus.md b/docs/bidders/appnexus.md
index 8b706adc122..e4032313f25 100644
--- a/docs/bidders/appnexus.md
+++ b/docs/bidders/appnexus.md
@@ -15,7 +15,7 @@ The AppNexus endpoint expects `imp.displaymanagerver` to be populated for mobile
requests, however not all SDKs will populate this field. If the `imp.displaymanagerver` field
is not supplied for an `imp`, but `request.app.ext.prebid.source`
and `request.app.ext.prebid.version` are supplied, the adapter will fill in a value for
-`diplaymanagerver`. It will concatonate the two `app` fields as `